hibernate懶載入

dawn009發表於2015-04-30
什麼是hibernate懶載入?所謂懶載入(lazy)就是延時載入,延遲載入。
什麼時候用懶載入呢?只能回答要用懶載入的時候就用懶載入。
為什麼要用懶載入呢?就是當我們要訪問的資料量過大時,明顯用快取不太合適,因為記憶體容量有限,為了減少併發量,減少系統資源的消耗,我們讓資料在需要的時候才進行載入,這時我們就用到了懶載入。


比如部門PO和員工PO,部門與員工1對多,如果lazy設定為 false,那麼只要載入了一個部門的po,就會根據一對多配置的關係把所有員工的po也載入出來。但是實際上有時候只是需要用到部門的資訊,不需要用到員工的資訊,這時員工po的載入就等於浪費資源。如果lazy設定為true,那麼只有當你訪問部門po的員工資訊時候才回去載入員工的po的資訊。
Hibernate 3.0中lazy有三個值:true,false,proxy,預設的是lazy="proxy"。
具體設定成什麼要看業務的需求,並不是說哪個設定就是最好的,到低要怎樣設定就要看你的實際需求了。

假如在student物件中包含一個head物件:
1. 如果確定在用student物件的時候就要用到head物件裡的屬性,那你就設定立即載入,因為設定立即載入那麼在查詢student的同時就會查詢 student的head,hibernate就會在查詢的時候關聯兩張表從而生成的sql就可能只有一條。
2. 如果你設定的是延遲載入,那麼肯定會要生成1+N條sql語句:其中“1”是查詢student的語句,“N”是根據N個student的id去查詢head的N條語句。而且,延遲載入是要用到的時候才去執行查詢,這樣系統判斷那裡需要載入,那裡不需要載入也需要時間,效能上肯定就不如立即載入了。
3. 如果有的地方需要用到student的時候才用到head屬性,那麼你就設定成延遲載入,因為查詢2張表的資料肯定要比查詢1張表的資料消耗大。

延遲載入機制是為了避免一些無謂的效能開銷而提出來的,所謂延遲載入就是當在真正需要資料的時候,才真正執行資料載入操作。在Hibernate中提供了對實體物件的延遲載入以及對集合的延遲載入,另外在Hibernate3中還提供了對屬性的延遲載入。


A 實體物件的延遲載入
如果想對實體物件使用延遲載入,必須要在實體的對映配置檔案中進行相應的配置,如下所示:
Xml程式碼
  1. <hibernate-mapping>  
  2.     <class name="com.mixele.entity.User" table="user" lazy="true">  
  3.         ......   
  4.     class>  
  5. hibernate-mapping>  

透過將class的lazy屬性設定為true,來開啟實體的延遲載入特性。如果我們執行下面的程式碼:
Java程式碼
  1. User user=(User)session.load(User.class,"1");//(1)這裡load()方法返回User物件的代理類物件   
  2. System.out.println(user.getName());//(2)透過CGLIB賦予的回撥機制進行賦值  

當執行到(1)處時,Hibernate並沒有發起對資料的查詢,如果此時透過一些除錯工具,觀察此時user物件的記憶體快照,會驚奇的發現,此時返回的 可能是User$EnhancerByCGLIB$$bede8986型別的物件,而且其屬性為null。這是怎麼回事?session.load()方 法會返回實體物件的代理類物件,這裡所返回的物件型別就是User物件的代理類物件。在Hibernate中透過使用 CGLIB,來實現動態構造一個目標物件的代理類物件,並且在代理類物件中包含目標物件的所有屬性和方法,而且所有屬性均被賦值為null。透過偵錯程式顯 示的記憶體快照,可以看出此時真正的User物件,是包含在代理物件的CGLIB$CALBACK_0.target屬性中。
當執行到(2)處時,此時呼叫user.getName()方法,這時透過CGLIB賦予的回撥機制,實際上呼叫 CGLIB$CALBACK_0.getName()方法,當呼叫該方法時,Hibernate會首先檢查CGLIB$CALBACK_0.target 屬性是否為null,如果不為空,則呼叫目標物件的getName方法,如果為空,則會發起資料庫查詢,生成類似這樣的SQL語句:select * from user where id='1';來查詢資料,並構造目標物件,並且將它賦值到CGLIB$CALBACK_0.target屬性中。
這樣,透過一箇中間代理物件,Hibernate實現了實體的延遲載入,只有當使用者真正發起獲得實體物件屬性的動作時,才真正會發起資料庫查詢操作。所以 實體的延遲載入是用透過中間代理類完成的,所以只有session.load()方法才會利用實體延遲載入,因為只有session.load()方法才 會返回實體類的代理類物件。


B 集合型別的延遲載入
在Hibernate的延遲載入機制中,針對集合型別的應用,意義是最為重大的,因為這有可能使效能得到大幅度的提高,為此Hibernate進行了大量的努力,其中包括對JDK Collection的獨立實現,在一對多關聯中,定義的用來容納關聯物件的Set集合,並不是java.util.Set型別或其子型別,而是 net.sf.hibernate.collection.Set型別,透過使用自定義集合類的實現,Hibernate實現了集合型別的延遲載入。為了對集合型別使用延遲載入,必須如下配置實體類的關於關聯的部分:
Xml程式碼
  1. <hibernate-mapping>  
  2.     <class name="com.mixele.entity.User" table="user">  
  3.         ......   
  4.         <set name="addresses" table="address" lazy="true" inverse="true">  
  5.             <key column="user_id"/>  
  6.             <one-to-many class="com.mixele.entity.Arrderss"/>  
  7.         set>  
  8.     class>  
  9. hibernate-mapping>  


透過將元素的lazy屬性設定為true來開啟集合型別的延遲載入特性。看下面的程式碼:
Java程式碼
  1. User user=(User)session.load(User.class,"1");   
  2. Collection addset=user.getAddresses();//(1)並不會發起對關聯資料的查詢來載入關聯資料   
  3. Iterator it=addset.iterator();//(2)查詢符合條件的實體物件   
  4. while(it.hasNext()) {   
  5.     Address address=(Address)it.next();   
  6.     System.out.println(address.getAddress());   
  7. }  

當程式執行到(1)處時,並不會發起對關聯資料的查詢來載入關聯資料,只有執行到(2)處時,真正的資料讀取操作才會開始,這時Hibernate會根據快取中符合條件的資料索引,來查詢符合條件的實體物件。
這裡引入了一個全新的概念——資料索引,下面首先將說明什麼是資料索引。在Hibernate中對集合型別進行快取時,是分兩部分進行快取的,首先快取集 閤中所有實體的id列表,然後快取實體物件,這些實體物件的id列表,就是所謂的資料索引。當查詢資料索引時,如果沒有找到對應的資料索引,這時就會執行 一條select SQL語句,獲得符合條件的資料,並構造實體物件集合和資料索引,然後返回實體物件的集合,並且將實體物件和資料索引納入Hibernate的快取之中。 另一方面,如果找到對應的資料索引,則從資料索引中取出id列表,然後根據id在快取中查詢對應的實體,如果找到就從快取中返回,如果沒有找到,在發起 select SQL查詢。在這裡我們看出了另外一個問題,這個問題可能會對效能產生影響,這就是集合型別的快取策略。如果如下配置集合型別:
Xml程式碼
  1. <hibernate-mapping>  
  2.     <class name="com.mixele.entity.User" table="user">  
  3.         ......   
  4.         <set name="addresses" table="address" lazy="true" inverse="true">  
  5.             <cache usage="read-only"/> ?<!--只會對資料索引進行快取--&gt  
  6.             <key column="user_id"/>  
  7.             <one-to-many class="com.mixele.entity.Arrderss"/>  
  8.         set>  
  9.     class>  
  10. hibernate-mapping>  

這裡應用了配置,如果採用這種策略來配置集合型別,Hibernate將只會對資料索引進行快取,而不會對集合中的實體物件進行快取。如上配置執行下面的程式碼:
Java程式碼
  1. User user=(User)session.load(User.class,"1");   
  2. Collection addset=user.getAddresses();   
  3. Iterator it=addset.iterator();   
  4. while(it.hasNext()) {   
  5.     Address address=(Address)it.next();   
  6.     System.out.println(address.getAddress());   
  7. }   
  8. System.out.println("Second query……");   
  9. User user2=(User)session.load(User.class,"1");   
  10. Collection it2=user2.getAddresses();   
  11. while(it2.hasNext()) {   
  12.     Address address2=(Address)it2.next();   
  13.     System.out.println(address2.getAddress());   
  14. }  

執行這段程式碼,會得到類似下面的輸出:
Select * from user where id='1';
Select * from address where user_id='1';
Tianjin
Dalian
Second query……
Select * from address where id='1';
Select * from address where id='2';
Tianjin
Dalian

可以看到,當第二次執行查詢時,執行了兩條對address表的查詢操作,為什麼會這樣呢?這是因為當第一次載入實體後,根據集合型別快取策略的配置,只 對集合資料索引進行了快取,而並沒有對集合中的實體物件進行快取,所以在第二次再次載入實體時,Hibernate找到了對應實體的資料索引,但是根據數 據索引,卻無法在快取中找到對應的實體,所以Hibernate根據找到的資料索引發起了兩條select SQL的查詢操作,這裡造成了對效能的浪費,怎樣才能避免這種情況呢?必須對集合型別中的實體也指定快取策略,對集合型別進行配置:
Xml程式碼
  1. <hibernate-mapping>  
  2.     <class name="com.mixele.entity.User" table="user">  
  3.         ......   
  4.         <set name="addresses" table="address" lazy="true" inverse="true">  
  5.             <cache usage="read-write"/>  
  6.             <key column="user_id"/>  
  7.             <one-to-many class="com.mixele.entity.Arrderss"/>  
  8.         set>  
  9.     class>  
  10. hibernate-mapping>  

此時Hibernate會對集合型別中的實體也進行快取,再次執行上面的程式碼,將會得到類似如下的輸出:
Select * from user where id='1';
Select * from address where user_id='1';
Tianjin
Dalian
Second query……
Tianjin
Dalian

這時將不會再有根據資料索引進行查詢的SQL語句,因為此時可以直接從快取中獲得集合型別中存放的實體物件。


C 屬性延遲載入
在Hibernate3中,引入了一種新的特性——屬性的延遲載入,這個機制又為獲取高效能查詢提供了有力的工具。在大資料物件讀取時,假設在User對 象中有一個resume欄位,該欄位是一個java.sql.Clob型別,包含了使用者的簡歷資訊,當載入該物件時,不得不每一次都要載入這個欄位,而不 論是否真的需要它,而且這種大資料物件的讀取本身會帶來很大的效能開銷。在Hibernate2中,只有透過面向效能的粒度細分,來分解User類,來解 決這個問題,但是在Hibernate3中,可以透過屬性延遲載入機制,來使我們獲得只有當我們真正需要操作這個欄位時,才去讀取這個欄位資料的能力,為 此必須如下配置實體類:
Xml程式碼
  1. <hibernate-mapping>  
  2.     <class name="com.mixele.entity.User" table="user">  
  3.         ......   
  4.         <property name="resume" type="java.sql.Clob" column="resume" lazy="true"/>  
  5.     class>  
  6. hibernate-mapping>  

透過對元素的lazy屬性設定true來開啟屬性的延遲載入,在Hibernate3中為了實現屬性的延遲載入,使用了 類增強器來對實體類的Class檔案進行強化處理,透過增強器的增強,將CGLIB的回撥機制邏輯,加入實體類,這裡我們可以看出屬性的延遲載入,還是通 過CGLIB來實現的。CGLIB是Apache的一個開源工程,這個類庫可以操縱java類的位元組碼,根據位元組碼來動態構造符合要求的類物件。根據上面 的配置我們執行下面的程式碼:
Java程式碼
  1. String sql="from User user where user.name='zx'";   
  2. Query query=session.createQuery(sql); //(1)Select id,age,name from user where name='zx';   
  3. List list=query.list();   
  4. for(int i=0;i
  5.     User user=(User)list.get(i);   
  6.     System.out.println(user.getName());   
  7.     System.out.println(user.getResume());//(2)Select resume from user where id='1';   
  8. }  

當執行到(1)處時,會生成類似如下的SQL語句:
Select id,age,name from user where name='zx';
這時Hibernate會檢索User實體中所有非延遲載入屬性對應的欄位資料,當執行到(2)處時,會生成類似如下的SQL語句:Select resume from user where id='1';
這時會發起對resume欄位資料真正的讀取操作。
Hibernate提供了懶載入策略,配合open session in view可以避免在不需要的時候載入關聯物件。
問題是很多情況下,同一個物件可能需要在不同的場合使用,有些場合可能需要一次性載入所有的關係物件。比如在使用者登入時,需要一次獲取使用者的所有關聯物件,而在使用者CRUD操作時,需要儘量使用懶載入避免載入不需要的物件。
可以將lazy設為true,針對登入請求提供一個不同的介面來解決上述問題,但是如果系統中存在大量這種情況,有什麼比較好的辦法來解決呢?只要你正確使用Spring,如果不涉及遠端的問題,OpenSessionView就可以解決了。


出處:http://blog.sina.com.cn/s/blog_5dab98f40100j5zd.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29119536/viewspace-1613642/,如需轉載,請註明出處,否則將追究法律責任。

相關文章