什么是hibernate懶加載?什么時候用懶加載?為什么要用懶加載?(轉)


所謂懶加載(lazy)就是延時加載,延遲加載。

什么時候用懶加載呢,我只能回答要用懶加載的時候就用懶加載。

至於為什么要用懶加載呢,就是當我們要訪問的數據量過大時,明顯用緩存不太合適,

因為內存容量有限 ,為了減少並發量,減少系統資源的消耗,

我們讓數據在需要的時候才進行加載,這時我們就用到了懶加載。

比如部門ENTITY和員工ENTITY,部門與員工1對多,如果lazy設置為 false,那么只要加載了一個部門的po,就會根據一對多配置的關系把所有員工的po也加載出來。但是實際上有時候只是需要用到部門的信息,不需要用到 員工的信息,這時員工po的加載就等於浪費資源。如果lazy設置為true,那么只有當你訪問部門po的員工信息時候才回去加載員工的po的信息。

 

hibernate3.0中lazy有三個值,true,false,proxy,默認的是lazy="proxy".
具體設置成什么要看你的需求,並不是說哪個設置就是最好的。
假如在student對象中包含一個head對象
如果你確定在用student對象的時候就要用到head對象里的屬性,那你就設置立即加載,因為設置立即加載那么在查詢student的同時就會查詢 student的head,hibernate就會在查詢的時候關聯兩張表從而生成的sql就可能只有一條。而如果你設置的是延遲加載,那么肯定會要生成 1+N條sql語句:其中“1”是查詢student的語句,“N”是根據N個student的id去查詢head的N條語句。而且,延遲加載是要用到的 時候才去執行查詢,這樣系統判斷那里需要加載,那里不需要加載也需要時間,性能上肯定就不如立即加載了!
如果,你是有的地方需要用到student的時候才用到head屬性,那么你就設置成延遲加載,因為查詢2張表的數據肯定要比查詢1張表的數據消耗大。
到低要怎樣設置就要看你的實際需求了

延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要數據的時候,才真正執行數據加載操作。在Hibernate中提供了對實體對象的延遲加載以及對集合的延遲加載,另外在Hibernate3中還提供了對屬性的延遲加載。
A、實體對象的延遲加載 
如果想對實體對象使用延遲加載,必須要在實體的映射配置文件中進行相應的配置,如下所示:
<hibernate-mapping>
 <class name=”com.neusoft.entity.User” table=”user” lazy=”true”>
    ……
 </class>
</hibernate-mapping>
     通過將class的lazy屬性設置為true,來開啟實體的延遲加載特性。如果我們運行下面的代碼:
    User user=(User)session.load(User.class,”1”);(1)
    System.out.println(user.getName());(2)
  當運行到(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實現了集合類型的延遲加載。為了 對集合類型使用延遲加載,必須如下配置實體類的關於關聯的部分:
<hibernate-mapping>
   <class name=”com.neusoft.entity.User” table=”user”>
     ……
    <set name=”addresses” table=”address” lazy=”true” inverse=”true”>
     <key column=”user_id”/>
      <one-to-many class=”com.neusoft.entity.Arrderss”/>
    </set>
   </class>
</hibernate-mapping>
   通過將<set>元素的lazy屬性設置為true來開啟集合類型的延遲加載特性。看下面的代碼:
 User user=(User)session.load(User.class,”1”);
 Collection addset=user.getAddresses();      (1)
 Iterator it=addset.iterator();               (2)
 while(it.hasNext()) {
  Address address=(Address)it.next();
  System.out.println(address.getAddress());
 }
     當程序執行到(1)處時,並不會發起對關聯數據的查詢來加載關聯數據,只有運行到(2)處時,真正的數據讀取操作才會開始,這時Hibernate會根據緩存中符合條件的數據索引,來查找符合條件的實體對象。
     這里引入了一個全新的概念——數據索引,下面首先將說明什么是數據索引。在Hibernate中對集合類型進行緩存時,是分兩部分進行緩存的,首先緩存 集合中所有實體的id列表,然后緩存實體對象,這些實體對象的id列表,就是所謂的數據索引。當查找數據索引時,如果沒有找到對應的數據索引,這時就會一 條select SQL的執行,獲得符合條件的數據,並構造實體對象集合和數據索引,然后返回實體對象的集合,並且將實體對象和數據索引納入Hibernate的緩存之 中。另一方面,如果找到對應的數據索引,則從數據索引中取出id列表,然后根據id在緩存中查找對應的實體,如果找到就從緩存中返回,如果沒有找到,在發 起select SQL查詢。在這里我們看出了另外一個問題,這個問題可能會對性能產生影響,這就是集合類型的緩存策略。如果如下配置集合類型:
<hibernate-mapping>
   <class name=”com.neusoft.entity.User” table=”user”>
    …
    <set name=”addresses” table=”address” lazy=”true” inverse=”true”>
      <cache usage=”read-only”/>
      <key column=”user_id”/>
      <one-to-many class=”com.neusoft.entity.Arrderss”/>
    </set>
   </class>
</hibernate-mapping>
     這里應用了<cache usage=”read-only”/>配置,如果采用這種策略來配置集合類型,Hibernate將只會對數據索引進行緩存,而不會對集合中的實體對象進行緩存。如上配置運行下面的代碼:
 User user=(User)session.load(User.class,”1”);
 Collection addset=user.getAddresses();      
 Iterator it=addset.iterator();               
 while(it.hasNext()) {
  Address address=(Address)it.next();
  System.out.println(address.getAddress());
 }
 System.out.println(“Second query……”);
 User user2=(User)session.load(User.class,”1”);
 Collection it2=user2.getAddresses();
 while(it2.hasNext()) {
  Address address2=(Address)it2.next();
  System.out.println(address2.getAddress());
 }

  運行這段代碼,會得到類似下面的輸出:
   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的查詢操作,這里造成了對性能的浪費,怎樣才能避免這種情況呢?必須對集合類型中的實體也指定緩存策略,對集合類型進行配置:
<hibernate-mapping>
   <class name=”com.neusoft.entity.User” table=”user”>
    ……
     <set name=”addresses” table=”address” lazy=”true” inverse=”true”>
       <cache usage=”read-write”/>
       <key column=”user_id”/>
       <one-to-many class=”com.neusoft.entity.Arrderss”/>
     </set>
   </class>
</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中,可以通過屬性延遲加載機制,來使我們獲得只有當我們真正需要操作這個字段時,才去讀取這個字段數據的能力, 為此必須如下配置實體類:
 <hibernate-mapping>
  <class name=”com.neusoft.entity.User” table=”user”>
      ……
     <property name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/>
  </class>
 </hibernate-mapping>
     通過對<property>元素的lazy屬性設置true來開啟屬性的延遲加載,在Hibernate3中為了實現屬性的延遲加載,使用 了類增強器來對實體類的Class文件進行強化處理,通過增強器的增強,將CGLIB的回調機制邏輯,加入實體類,這里我們可以看出屬性的延遲加載,還是 通過CGLIB來實現的。CGLIB是Apache的一個開源工程,這個類庫可以操縱java類的字節碼,根據字節碼來動態構造符合要求的類對象。根據上 面的配置我們運行下面的代碼:
 String sql=”from User user where user.name=’zx’ ”;
 Query query=session.createQuery(sql);   (1)
 List list=query.list();
 for(int i=0;i<list.size();i++) {
  User user=(User)list.get(i);
  System.out.println(user.getName());
  System.out.println(user.getResume());   (2)
 }
  當執行到(1)處時,會生成類似如下的SQL語句:
 Select id,age,name from user where name=’zx’;
  這時Hibernate會檢索User實體中所有非延遲加載屬性對應的字段數據,當執行到(2)處時,會生成類似如下的SQL語句:
 Select resume from user where id=’1’;
這時會發起對resume字段數據真正的讀取操作。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM