檢索數(shù)據(jù),也就是查詢數(shù)據(jù)是在一個(gè)系統(tǒng)中必不可少的一個(gè)功能。檢索數(shù)據(jù)時(shí)的2個(gè)問(wèn)題:
由于篇幅原因,將內(nèi)容分為了兩部分:
Hibernate:檢索策略的學(xué)習(xí)1、Hibernate:檢索策略的學(xué)習(xí)2
第一部分講解了類級(jí)別的檢索策略以及1-N和N-N的檢索策略,在第二部分中將學(xué)習(xí)N-1和1-1的檢索策略,并對(duì)檢索策略進(jìn)行總結(jié)。
類級(jí)別可選的檢索策略包括立即檢索和延遲檢索, 默認(rèn)為延遲檢索。
類級(jí)別的檢索策略可以通過(guò) 元素的 lazy 屬性進(jìn)行設(shè)置。
如果程序加載一個(gè)對(duì)象的目的是為了訪問(wèn)它的屬性,可以采取立即檢索;如果程序加載一個(gè)持久化對(duì)象的目的是僅僅為了獲得它的引用,可以采用延遲檢索,但需要注意懶加載異常(LazyInitializationException:簡(jiǎn)單理解該異常就是Hibernate在使用延遲加載時(shí),并沒(méi)有將數(shù)據(jù)實(shí)際查詢出來(lái),而只是得到了一個(gè)代理對(duì)象,當(dāng)使用屬性的時(shí)候才會(huì)去查詢,而如果這個(gè)時(shí)候session關(guān)閉了,則會(huì)報(bào)該異常)!
下面通過(guò)一個(gè)例子來(lái)進(jìn)行講解:
在該Demo中,我們只需要使用一個(gè)Customer的對(duì)象即可,其中包含了id,name等屬性。
首先我們來(lái)測(cè)試一下元素的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)行測(cè)試(關(guān)于Junit的知識(shí)在這不多說(shuō),并不是重點(diǎn))。其中init方法是對(duì)SessionFactory、Session等進(jìn)行初始化,destroy方法是進(jìn)行關(guān)閉。
當(dāng)我們運(yùn)行testClassLevelStrategy()方法時(shí),會(huì)得到以下輸出結(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
通過(guò)控制臺(tái)的輸出結(jié)果,我們可以清楚的看到,當(dāng)我們執(zhí)行session.load(Customer.class, 1)
方法時(shí),Hibernate并沒(méi)有發(fā)送SQL語(yǔ)句,而只是返回了一個(gè)對(duì)象,通過(guò)調(diào)用getClass()方法,可以看到該對(duì)象class com.atguigu.hibernate.strategy.Customer_$$_javassist_1
是一個(gè)代理對(duì)象,并且當(dāng)調(diào)用customer.getCustomerId()
獲取ID的時(shí)候,也沒(méi)有發(fā)送SQL語(yǔ)句;當(dāng)我們這個(gè)再調(diào)用customer.getCustomerName()
方法來(lái)得到name的時(shí)候,這個(gè)時(shí)候才發(fā)送了SQL語(yǔ)句進(jìn)行真正的查詢,并且WHERE條件中帶上的就是ID。
如果我們?cè)?code>System.out.println(customer.getCustomerName());語(yǔ)句前插入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>
...
這個(gè)時(shí)候,我們?cè)龠\(yùn)行之前的單元測(cè)試代碼,控制臺(tái)會(huì)得到以下輸出結(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方法時(shí),會(huì)發(fā)送SQL語(yǔ)句,并且得到的不再是代理對(duì)象。這個(gè)時(shí)候就算我們?cè)谳敵鰊ame屬性之前將session關(guān)閉,也不會(huì)報(bào)錯(cuò)。
上文中對(duì)Hibernate的類級(jí)別的檢索策略進(jìn)行了總結(jié),當(dāng)然這些是建立在有一定基礎(chǔ)的前提下。需要注意的是:
我在之前的博客中對(duì)1-N和N-N有過(guò)學(xué)習(xí),所以我假設(shè)讀者已經(jīng)了解了Hibernate中關(guān)于1-N和N-N的映射。我們建立了Customer與Order的1-N關(guān)聯(lián)關(guān)系,表示一個(gè)顧客可以有多個(gè)訂單。
我們?cè)谟成湮募?,用元素?lái)配置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二級(jí)緩存時(shí)可以考慮使用立即檢索 |
extra | 未設(shè)置 (取默認(rèn)值select) | 采用加強(qiáng)延遲檢索策略,它盡可能的延遲orders集合被初始化的時(shí)機(jī) |
true,extra or extra | 未設(shè)置 (取默認(rèn)值select) | lazy屬性決定采用的檢索策略,即決定初始化orders集合的時(shí)機(jī)。fetch屬性為select,意味 著通過(guò)select語(yǔ)句來(lái)初始化orders的集合,形式為SELECT * FROM orders WHERE customer _id IN (1,2,3,4) |
true,extra or extra | subselect | lazy屬性決定采用的檢索策略,即決定初始化orders集合的時(shí)機(jī)。fetch屬性為subselect,意味 著通過(guò)subselect語(yǔ)句來(lái)初始化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并沒(méi)有初始化,而是返回了一個(gè)集合代理對(duì)象。當(dāng)我們通過(guò)customer.getOrders().size()這段代碼真正要使用orders集合的時(shí)候,才發(fā)送SQL語(yǔ)句進(jìn)行查詢。
在延遲檢索(lazy屬性值為true)集合屬性時(shí),Hibernate在以下情況下初始化集合代理類實(shí)例:
下面我們將的lazy屬性修改為false,如<set name="orders" table="ORDERS" inverse="true" lazy="false">
。
修改完之后再執(zhí)行測(cè)試代碼,輸出結(jié)果也就很明顯了,在調(diào)用load()方法時(shí),會(huì)先執(zhí)行SQL語(yǔ)句取出Customer以及相關(guān)聯(lián)的orders。
最后,提一下lazy的另一個(gè)取值extra。該取值與true類似,主要區(qū)別是增強(qiáng)延遲檢索策略能夠進(jìn)一步延遲Customer對(duì)象的orders集合代理實(shí)例的初始化時(shí)機(jī)。
首先我們將元素中的lazy設(shè)為extra。我們同樣的執(zhí)行上文中的單元測(cè)試代碼, 得到以下結(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
我們觀察第二個(gè)SQL語(yǔ)句。我們發(fā)現(xiàn)他并沒(méi)有對(duì)orders進(jìn)行初始化,而是通過(guò)使用一個(gè)count()函數(shù)。extra取值為增強(qiáng)的延遲檢索,該取值會(huì)盡可能的延遲集合初始化的時(shí)機(jī)。
例如:當(dāng)我們將lazy設(shè)置為true(延遲檢索),而我們調(diào)用order.size()方法的時(shí)候,這個(gè)時(shí)候就會(huì)通過(guò)SQL將orders集合初始化。但現(xiàn)在我們用extra這個(gè)屬性,發(fā)現(xiàn)我們調(diào)用orders的size方法,并沒(méi)有初始化,而是通過(guò)了一個(gè)count函數(shù)。
所以我們得到結(jié)論:
增強(qiáng)延遲檢索策略能進(jìn)一步延遲 Customer 對(duì)象的 orders 集合代理實(shí)例的初始化時(shí)機(jī):
但其實(shí)我們?cè)趯?shí)際的開發(fā)過(guò)程中,當(dāng)我們要用到size或者contains等方法的時(shí)候,基本上代表我們就要用到集合部分的屬性。如果我們選用extra的話,反倒會(huì)多發(fā)送SQL語(yǔ)句。
關(guān)于extra的其他點(diǎn)大家可以自己進(jìn)行一些測(cè)試,比較簡(jiǎn)單方便。
更多建議: