檢索數(shù)據(jù),也就是查詢數(shù)據(jù)是在一個系統(tǒng)中必不可少的一個功能。檢索數(shù)據(jù)時的2個問題:
由于篇幅原因,將內(nèi)容分為了兩部分:
Hibernate:檢索策略的學(xué)習(xí)1、Hibernate:檢索策略的學(xué)習(xí)2
第一部分講解了類級別的檢索策略以及1-N和N-N的檢索策略,在第二部分中將學(xué)習(xí)N-1和1-1的檢索策略,并對檢索策略進(jìn)行總結(jié)。
類級別可選的檢索策略包括立即檢索和延遲檢索, 默認(rèn)為延遲檢索。
類級別的檢索策略可以通過 元素的 lazy 屬性進(jìn)行設(shè)置。
如果程序加載一個對象的目的是為了訪問它的屬性,可以采取立即檢索;如果程序加載一個持久化對象的目的是僅僅為了獲得它的引用,可以采用延遲檢索,但需要注意懶加載異常(LazyInitializationException:簡單理解該異常就是Hibernate在使用延遲加載時,并沒有將數(shù)據(jù)實際查詢出來,而只是得到了一個代理對象,當(dāng)使用屬性的時候才會去查詢,而如果這個時候session關(guān)閉了,則會報該異常)!
下面通過一個例子來進(jìn)行講解:
在該Demo中,我們只需要使用一個Customer的對象即可,其中包含了id,name等屬性。
首先我們來測試一下元素的lazy屬性為true的情況,也就是默認(rèn)情況(不設(shè)置)。
public class HibernateTest {
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@Before
public void init() {
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
@After
public void destroy() {
transaction.commit();
session.close();
sessionFactory.close();
}
@Test
public void testClassLevelStrategy() {
Customer customer = (Customer) session.load(Customer.class, 1);
System.out.println(customer.getClass());
System.out.println(customer.getCustomerId());
System.out.println(customer.getCustomerName());
}
}
看一下上面的代碼,該代碼是利用Junit進(jìn)行測試(關(guān)于Junit的知識在這不多說,并不是重點(diǎn))。其中init方法是對SessionFactory、Session等進(jìn)行初始化,destroy方法是進(jìn)行關(guān)閉。
當(dāng)我們運(yùn)行testClassLevelStrategy()方法時,會得到以下輸出結(jié)果:
class com.atguigu.hibernate.strategy.Customer_$$_javassist_1
1
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
from
CUSTOMERS customer0_
where
customer0_.CUSTOMER_ID=?
AA
通過控制臺的輸出結(jié)果,我們可以清楚的看到,當(dāng)我們執(zhí)行session.load(Customer.class, 1)
方法時,Hibernate并沒有發(fā)送SQL語句,而只是返回了一個對象,通過調(diào)用getClass()方法,可以看到該對象class com.atguigu.hibernate.strategy.Customer_$$_javassist_1
是一個代理對象,并且當(dāng)調(diào)用customer.getCustomerId()
獲取ID的時候,也沒有發(fā)送SQL語句;當(dāng)我們這個再調(diào)用customer.getCustomerName()
方法來得到name的時候,這個時候才發(fā)送了SQL語句進(jìn)行真正的查詢,并且WHERE條件中帶上的就是ID。
如果我們在System.out.println(customer.getCustomerName());
語句前插入session.close()
將Session關(guān)閉,就能看到之前上文中提到的懶加載異常。
為了讓Customer類可以立即檢索,我們要修改Customer.hbm.xml文件,在標(biāo)簽中加入lazy="false"。
<hibernate-mapping package="com.atguigu.hibernate.strategy">
<class name="Customer" table="CUSTOMERS" lazy="false">
<id name="customerId" type="java.lang.Integer">
<column name="CUSTOMER_ID" />
<generator class="native" />
</id>
...
這個時候,我們再運(yùn)行之前的單元測試代碼,控制臺會得到以下輸出結(jié)果:
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
from
CUSTOMERS customer0_
where
customer0_.CUSTOMER_ID=?
class com.atguigu.hibernate.strategy.Customer
1
AA
我們可以看到,當(dāng)調(diào)用load方法時,會發(fā)送SQL語句,并且得到的不再是代理對象。這個時候就算我們在輸出name屬性之前將session關(guān)閉,也不會報錯。
上文中對Hibernate的類級別的檢索策略進(jìn)行了總結(jié),當(dāng)然這些是建立在有一定基礎(chǔ)的前提下。需要注意的是:
我在之前的博客中對1-N和N-N有過學(xué)習(xí),所以我假設(shè)讀者已經(jīng)了解了Hibernate中關(guān)于1-N和N-N的映射。我們建立了Customer與Order的1-N關(guān)聯(lián)關(guān)系,表示一個顧客可以有多個訂單。
我們在映射文件中,用元素來配置1-N關(guān)聯(lián)以及N-N關(guān)聯(lián)關(guān)系。元素有l(wèi)azy、fetch和batch-size屬性。
lazy屬性 (默認(rèn)值true) | fetch屬性 (默認(rèn)值select) | 檢索策略 |
---|---|---|
true | 未設(shè)置 (取默認(rèn)值select) | 采用延遲檢索策略,這是默認(rèn)的檢索策略,也是優(yōu)先考慮使用的檢索策略 |
false | 未設(shè)置 (取默認(rèn)值select) | 采用立即索策略,當(dāng)使用Hibernate二級緩存時可以考慮使用立即檢索 |
extra | 未設(shè)置 (取默認(rèn)值select) | 采用加強(qiáng)延遲檢索策略,它盡可能的延遲orders集合被初始化的時機(jī) |
true,extra or extra | 未設(shè)置 (取默認(rèn)值select) | lazy屬性決定采用的檢索策略,即決定初始化orders集合的時機(jī)。fetch屬性為select,意味 著通過select語句來初始化orders的集合,形式為SELECT * FROM orders WHERE customer _id IN (1,2,3,4) |
true,extra or extra | subselect | lazy屬性決定采用的檢索策略,即決定初始化orders集合的時機(jī)。fetch屬性為subselect,意味 著通過subselect語句來初始化orders的集合,形式為SELECT * FROM orders WHERE customer_id IN (SELECT id FROM customers) |
true | join | 采采用迫切左外連接策略 |
我們現(xiàn)在開始研究一下關(guān)于元素的lazy屬性。
首先我們看一下延遲檢索,也就是屬性的lazy為true或者不設(shè)置的情況下:
@Test
public void testOne2ManyLevelStrategy() {
Customer customer = (Customer) session.get(Customer.class, 1);
System.out.println(customer.getCustomerName());
System.out.println(customer.getOrders().getClass());
System.out.println(customer.getOrders().size());
}
下面是控制的輸出結(jié)果
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
from
CUSTOMERS customer0_
where
customer0_.CUSTOMER_ID=?
AA
class org.hibernate.collection.internal.PersistentSet
Hibernate:
select
orders0_.CUSTOMER_ID as CUSTOMER3_0_1_,
orders0_.ORDER_ID as ORDER_ID1_1_1_,
orders0_.ORDER_ID as ORDER_ID1_1_0_,
orders0_.ORDER_NAME as ORDER_NA2_1_0_,
orders0_.CUSTOMER_ID as CUSTOMER3_1_0_
from
ORDERS orders0_
where
orders0_.CUSTOMER_ID=?
3
從結(jié)果中可以明顯的看出,Hibernate使用了延遲檢索。其中的orders并沒有初始化,而是返回了一個集合代理對象。當(dāng)我們通過customer.getOrders().size()這段代碼真正要使用orders集合的時候,才發(fā)送SQL語句進(jìn)行查詢。
在延遲檢索(lazy屬性值為true)集合屬性時,Hibernate在以下情況下初始化集合代理類實例:
下面我們將的lazy屬性修改為false,如<set name="orders" table="ORDERS" inverse="true" lazy="false">
。
修改完之后再執(zhí)行測試代碼,輸出結(jié)果也就很明顯了,在調(diào)用load()方法時,會先執(zhí)行SQL語句取出Customer以及相關(guān)聯(lián)的orders。
最后,提一下lazy的另一個取值extra。該取值與true類似,主要區(qū)別是增強(qiáng)延遲檢索策略能夠進(jìn)一步延遲Customer對象的orders集合代理實例的初始化時機(jī)。
首先我們將元素中的lazy設(shè)為extra。我們同樣的執(zhí)行上文中的單元測試代碼, 得到以下結(jié)果:
Hibernate:
select
customer0_.CUSTOMER_ID as CUSTOMER1_0_0_,
customer0_.CUSTOMER_NAME as CUSTOMER2_0_0_
from
CUSTOMERS customer0_
where
customer0_.CUSTOMER_ID=?
AA
class org.hibernate.collection.internal.PersistentSet
Hibernate:
select
count(ORDER_ID)
from
ORDERS
where
CUSTOMER_ID =?
3
我們觀察第二個SQL語句。我們發(fā)現(xiàn)他并沒有對orders進(jìn)行初始化,而是通過使用一個count()函數(shù)。extra取值為增強(qiáng)的延遲檢索,該取值會盡可能的延遲集合初始化的時機(jī)。
例如:當(dāng)我們將lazy設(shè)置為true(延遲檢索),而我們調(diào)用order.size()方法的時候,這個時候就會通過SQL將orders集合初始化。但現(xiàn)在我們用extra這個屬性,發(fā)現(xiàn)我們調(diào)用orders的size方法,并沒有初始化,而是通過了一個count函數(shù)。
所以我們得到結(jié)論:
增強(qiáng)延遲檢索策略能進(jìn)一步延遲 Customer 對象的 orders 集合代理實例的初始化時機(jī):
但其實我們在實際的開發(fā)過程中,當(dāng)我們要用到size或者contains等方法的時候,基本上代表我們就要用到集合部分的屬性。如果我們選用extra的話,反倒會多發(fā)送SQL語句。
關(guān)于extra的其他點(diǎn)大家可以自己進(jìn)行一些測試,比較簡單方便。
更多建議: