Hibernate Lazy&Fetch
1. 前言
本節(jié)和大家一起聊聊 Hibernate 中的 Lazy 和 Fetch 的區(qū)別,及兩者適合的開發(fā)場景。通過本節(jié)課程的學(xué)習(xí),你將了解到:
- 什么是延遲加載;
- 延遲加載的意義。
2. 又見 get() 和 load()
Session 對象提供了 2 個方法用來查詢 :
- get() 方法;
- load()方法。
如果僅以結(jié)果為導(dǎo)向,則無法分辨兩者的差異性。
兩者如同雙胞胎,外觀雖然差異不大,但其神韻各有千秋。仔細(xì)辨別,便能發(fā)現(xiàn)屬于各自的特征。
真相只有一個,查明真相的手段,也只有一種:讓代碼回答。
2.1 測試 get() 方法
Student stu=null;
try{
// 打開事務(wù)
transaction = session.beginTransaction();
//使用get()方法查詢學(xué)號為1的學(xué)生
stu=(Student)session.get(Student.class, new Integer(1));
System.out.println("--------------輸出學(xué)生信息------------------");
System.out.println(stu.getStuName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
System.out.println("***********關(guān)閉Session之后******************");
System.out.println(stu.getStuName());
如上測試代碼,和上一節(jié)課程的 get() 方法測試有區(qū)別:
- 調(diào)用 **get()** 方法查詢編號為 1 的學(xué)生數(shù)據(jù),但會在輸出學(xué)生數(shù)據(jù)之前先輸出一條提示語句,作為標(biāo)識分割線;
- 關(guān)閉 Session 對象后繼續(xù)使用查詢出來的學(xué)生數(shù)據(jù)。
查看代碼運行結(jié)果:
select
student0_.stuId as stuId1_0_0_,
student0_.stuName as stuName2_0_0_,
student0_.stuPassword as stuPassw3_0_0_,
student0_.stuPic as stuPic4_0_0_,
student0_.stuSex as stuSex5_0_0_
from
Student student0_
where
student0_.stuId=?
--------------輸出學(xué)生信息------------------
Hibernate是老大
***********關(guān)閉Session之后******************
Hibernate是老大
結(jié)果能說明什么問題呢?
仔細(xì)分析輸出的日志信息:
-
調(diào)用 get() 方法時,Hibernate 就構(gòu)建了一條 Sql 語句。說明,調(diào)用 get() 方法時,Hibernate 就跑了一趟數(shù)據(jù)庫,并拿到了開發(fā)者指定的數(shù)據(jù);
-
關(guān)閉 Session 對象后,程序可以繼續(xù)使用學(xué)生數(shù)據(jù)。說明,通過 get() 方法獲得的數(shù)據(jù)已經(jīng)保存到程序運行的內(nèi)存中,不需要再依賴 Session。
想說明什么?不想說明什么?只是一個結(jié)論。
2.2 測試 load() 方法
把上面測試實例中的 get() 方法換成 load() 方法。
且運行實例:
Student stu=null;
try{
// 打開事務(wù)
transaction = session.beginTransaction();
//查詢學(xué)號為1的學(xué)生
stu=(Student)session.load(Student.class, new Integer(1));
System.out.println("--------------輸出學(xué)生信息------------------");
System.out.println(stu.getStuName());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
System.out.println("***********關(guān)閉Session之后******************");
System.out.println(stu.getStuName());
控制臺上查看實例使用結(jié)果:
--------------輸出學(xué)生信息------------------
Hibernate:
select
student0_.stuId as stuId1_0_0_,
student0_.stuName as stuName2_0_0_,
student0_.stuPassword as stuPassw3_0_0_,
student0_.stuPic as stuPic4_0_0_,
student0_.stuSex as stuSex5_0_0_
from
Student student0_
where
student0_.stuId=?
Hibernate是老大
***********關(guān)閉Session之后******************
Hibernate是老大
得到什么結(jié)論了嗎?
現(xiàn)在開始尋找區(qū)別。
不仔細(xì)觀察,會誤判沒有什么區(qū)別。
而其中有一個很明顯的區(qū)別就是:
調(diào)用 load ( ) 方法時,Hiberante 并沒有真正的行動,只有當(dāng)執(zhí)行到下面代碼時:
System.out.println(stu.getStuName());
Hibernate 才從容不迫地構(gòu)建 Sql 語句,往數(shù)據(jù)庫跑了一趟,獲得數(shù)據(jù),再輸出數(shù)據(jù)。
OK!再稍微改動一下測試代碼:
//會話對象
Student stu=null;
try {
// 打開事務(wù)
transaction = session.beginTransaction();
//查詢學(xué)號為1的學(xué)生
stu=(Student)session.load(Student.class, new Integer(1));
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
System.out.println("***********關(guān)閉Session之后******************");
System.out.println("--------------輸出學(xué)生信息------------------");
System.out.println(stu.getStuName());
輸出學(xué)生信息并不是在調(diào)用 load( ) 方法之后,而是關(guān)閉 Session 對象之后,結(jié)果又會怎樣?猜得出來嗎?
把你心中的猜想和下面的輸出結(jié)果比較一下。
沒想到吧,拋異常啦,拋異常沒什么大驚小怪的,異常是為了告訴你錯誤原因。查看異常信息,從中找出原因:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
省略其它若干……
不要望而生畏,都是紙老虎。找出關(guān)鍵詞:
could not initialize proxy - no Session
初看字面意思:不能初始化代理,沒有 Session 對象。
什么意思?
到了好好解釋這個原因的時候:
調(diào)用 load( ) 方法時,Hibernate 根本沒有如你所期望一樣,往數(shù)據(jù)庫跑。但是又不想讓你知道它沒去,或是怕你擔(dān)心它是否能完成這份工作。于是 Hibernate 為你提供了一個 代理對象。
這里會涉及到代理設(shè)計模式!為不影響主題學(xué)習(xí),代理設(shè)計模式相關(guān)內(nèi)容自己了解一下。
什么是代理對象?
通俗講就是說外觀和開發(fā)者所期望的對象一樣,但沒有實質(zhì)性數(shù)據(jù)。
再通俗點,就是一個替身。有其外形,而無內(nèi)涵。
再通俗講:哦,你已經(jīng)明白了。
Hibernate 這是演的哪一出?這不是擺明欺負(fù)人嗎?
別誤會,這是 Hibernate 的善意之舉!善意從何談起呀!別急!
只有當(dāng)開發(fā)者真正需要數(shù)據(jù)時:
System.out.println(stu.getStuName());
Hibernate 才會構(gòu)建 Sql 語句,往數(shù)據(jù)庫跑一趟,獲得真正的數(shù)據(jù)。
但是,執(zhí)行 Sql 語句是一定要在 Session 的生命周期之內(nèi),如果:
session.close();
通往數(shù)據(jù)庫的橋梁被拆了。Hibernate 也無能為力,只能以異常的方式告訴你:
no Session!臣妾做不到呀。
測試 get()、load() 方法的輸出結(jié)果已經(jīng)表明了兩者的差異性:
- get() 方法言行一致,說出手呀便出手。開發(fā)者一調(diào)用,便快馬加鞭,從數(shù)據(jù)庫中獲得數(shù)據(jù),保存到學(xué)生對象中,只要學(xué)生對象在,數(shù)據(jù)也就在;
- load() 方法,看起來倒像是說一套,做一套的主。并不會立馬行事,而是創(chuàng)建一個學(xué)生代理對象,提供和開發(fā)者期待的學(xué)生數(shù)據(jù)對象相同的方法接口,不影響開發(fā)者調(diào)用。只有當(dāng)開發(fā)者真正需要數(shù)據(jù)時,才會說,好的,我去看一下數(shù)據(jù)庫。
故而,使用 load() 時就需要特別注意,在 Hibernate 取數(shù)據(jù)庫之前,千萬別關(guān)閉通向數(shù)據(jù)庫的橋梁:Session 對象。
Session 家里有 2 個可用于查詢的兄弟:
- get() 是老實人,言行一致。
- load() 有點小調(diào)皮,有時搞點惡作劇,但心思并不壞。如果真正理解它的意圖,在特定的環(huán)境下,可能會感動到你。
其實兩兄弟都很有趣。
3. 延遲加載
延遲加載?不是在聊 get() 和 load() 方法嗎,不是聊得好好的嘛!咋的,中場休息呀。
3.1 什么是延遲加載
什么是延遲加載?前面的測試結(jié)論已經(jīng)給出了答案。
使用 Hibernate 獲取數(shù)據(jù)時,有時,Hibernate 并不急著去數(shù)據(jù)庫,而是等到開發(fā)者真正需要數(shù)據(jù)時才會跑一趟數(shù)據(jù)庫。
load() 方法 和 get() 方法的基礎(chǔ)區(qū)別:
- load() 支持延遲加載(Lazy);
意思是,別急,你需要時我再去拿數(shù)據(jù)。如果沒有拿到數(shù)據(jù),則會拋出異常。
- get() 方法不支持延遲加載,而是(Fetch),如果沒有拿到數(shù)據(jù),則返回 null 。
什么時候使用 get(),什么時候使用 load()。只有需求才能告訴你如何權(quán)衡,沒有絕對的忠告。
3.2 延遲加載的意義
答案很簡單:錯峰出行,需時索取。
數(shù)據(jù)庫系統(tǒng)的迎接能力終歸是有限的。面對同時有很多數(shù)據(jù)請求時,就會造成擁堵。并不是所有的數(shù)據(jù)請求會在它的邏輯中立即使用數(shù)據(jù)。
于是,就可以使用延遲加載技術(shù),暫緩數(shù)據(jù)請求,真正需要時,或錯開數(shù)據(jù)庫系統(tǒng)的訪問高峰期后再訪問。
在真實的企業(yè)級項目中,一個業(yè)務(wù)邏輯往往是借助于多個組件一起協(xié)作完成的。
Hibernate 作為數(shù)據(jù)請求框架,充當(dāng)數(shù)據(jù)提供者角色,本身并不處理數(shù)據(jù)。數(shù)據(jù)的使用延遲到了數(shù)據(jù)加工組件之中。
于是,Hibernate 用不著立即造訪數(shù)據(jù)庫,先給數(shù)據(jù)加工組件提供一個代理對象,等數(shù)據(jù)加工組件真正需要數(shù)據(jù)時再訪問數(shù)據(jù)庫也不遲。
延遲加載是 Hibernate 中的性能優(yōu)化技術(shù),不要誤會它是在使什么小心眼。完全是一番好意。
哲學(xué)上講世界是平衡的,一頭變輕,另一頭就會變重??偰芰肯牟蛔儭?/p>
延遲加載技術(shù)提供了一種性能優(yōu)化方式(變輕了),但在還沒有真正獲取數(shù)據(jù)之前,不能關(guān)閉 Session 對象(生命周期延長,變重了)算是平衡制約吧。
4. 小結(jié)
本節(jié)課聊到了 Hibernate 中一個很重要的概念:延遲加載,是一種性能優(yōu)化技術(shù)。讓開發(fā)者在真正需要數(shù)據(jù)的時候才進(jìn)入到數(shù)據(jù)庫。
Session 提供的 load() 方法支持延遲加載。但是,千萬別以為延遲加載僅僅是 load() 方法的專利。
延遲加載是性能優(yōu)化技術(shù),Hibernate 在設(shè)計時,凡是考慮有必要使用的地方都會有延遲加載的身影。