Hibernate延遲加載介紹

Hibernate延遲加載:

延遲加載機制是爲了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要數據的時候,才真正執行數據加載操作。在Hibernate中 提供了對實體對象的延遲加載以及對集合的延遲加載,另外在Hibernate3中還提供了對屬性的延遲加載。下面我們就分別介紹這些種類的 Hibernate延遲加載的細節。

A、Hibernate延遲加載之實體對象的延遲加載:

如果想對實體對象使用延遲加載,必須要在實體的映射配置文件中進行相應的配置,如下所示:

  1. <Hibernate-mapping><class  name =”com.neusoft.entity. User ”   
  2. table =” user ” lazy=” true ”>      
  3. ……</class></Hibernate-mapping> 

通過將class的lazy屬性設置爲true,來開啓實體的延遲加載特性。如果我們運行下面的代碼:

  1. User   user =( User )session. load ( User .class,”1”);(1)  
  2. System. out .println( user .getName());(2) 

當運行到(1)處時,Hibernate並沒有發起對數據的查詢,如果我們此時通過一些調試工具(比如JBuilder2005的Debug工 具),觀察此時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的延遲加載機制中,針對集合類型的應用,意義是最爲重大的,因爲這有可能使性能得到大幅度的提高,爲此Hibernate進行 了大量的努力,其中包括對JDK Collection的獨立實現,我們在一對多關聯中,定義的用來容納關聯對象的Set集合,並不是java.util.Set類型或其子類型,而是 net.sf.Hibernate.collection.Set類型,通過使用自定義集合類的實現,Hibernate實現了集合類型的延遲加載。爲了 對集合類型使用延遲加載,我們必須如下配置我們的實體類的關於關聯的部分:

  1. <Hibernate-mapping>      
  2. <class  name =”com.neusoft.entity. User ”  table =” user ”>…..  
  3. < set   name =”addresses”  table =”address” lazy=” true ”   
  4. inverse=” true ”>< key   column =”user_id”/>  
  5. <one- to -many class=”com.neusoft.entity.Arrderss”/></ set >      
  6. </class></Hibernate-mapping> 

通過將<set>元素的lazy屬性設置爲true來開啓集合類型的延遲加載特性。我們看下面的代碼:

  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()){Address address=(Address)it. next ();  
  5. System. out .println(address.getAddress());} 

當程序執行到(1)處時,這時並不會發起對關聯數據的查詢來加載關聯數據,只有運行到(2)處時,真正的數據讀取操作纔會開始,這時 Hibernate會根據緩存中符合條件的數據索引,來查找符合條件的實體對象。

這裏我們引入了一個全新的概念——數據索引,下面我們首先將接一下什麼是數據索引。在Hibernate中對集合類型進行緩存時,是分兩部分進行緩 存的,首先緩存集合中所有實體的id列表,然後緩存實體對象,這些實體對象的id列表,就是所謂的數據索引。當查找數據索引時,如果沒有找到對應的數據索 引,這時就會一條select SQL的執行,獲得符合條件的數據,並構造實體對象集合和數據索引,然後返回實體對象的集合,並且將實體對象和數據索引納入Hibernate的緩存之 中。另一方面,如果找到對應的數據索引,則從數據索引中取出id列表,然後根據id在緩存中查找對應的實體,如果找到就從緩存中返回,如果沒有找到,在發 起select SQL查詢。在這裏我們看出了另外一個問題,這個問題可能會對性能產生影響,這就是集合類型的緩存策略。如果我們如下配置集合類型:

  1. <Hibernate-mapping>      
  2. <class  name =”com.neusoft.entity. User ”  table =” user ”>…..  
  3. < set   name =”addresses”  table =”address” lazy=” true ”   
  4. inverse=” true ”><cache usage=” read - only ”/>< key   column =”user_id”/>  
  5. <one- to -many class=”com.neusoft.entity.Arrderss”/></ set >      
  6. </class></Hibernate-mapping> 

這裏我們應用了<cache usage=”read-only”/>配置,如果採用這種策略來配置集合類型,Hibernate將只會對數據索引進行緩存,而不會對集合中的實 體對象進行緩存。如上配置我們運行下面的代碼:

  1. User   user =( User )session. load ( User .class,”1”);  
  2. Collection addset= user .getAddresses();        
  3. Iterator it=addset.iterator();                 
  4. while(it.hasNext()){Address address=(Address)it. next ();System. out .println(address.getAddress());}  
  5. System. out .println(“ Second  query……”);  
  6. User  user2=( User )session. load ( User .class,”1”);  
  7. Collection it2=user2.getAddresses();while(it2.hasNext())  
  8. {Address address2=(Address)it2. next ();  
  9. System. out .println(address2.getAddress());} 

運行這段代碼,會得到類似下面的輸出:

  1. Select  *  from   user   where  id=’1’;  
  2. Select  *  from  address  where  user_id=’1’;  
  3. TianjinDalianSecond query…… Select  *  from  address  where  id=’1’;  
  4. Select  *  from  address  where  id=’2’;  
  5. TianjinDalian 

我們看到,當第二次執行查詢時,執行了兩條對address表的查詢操作,爲什麼會這樣?這是因爲當第一次加載實體後,根據集合類型緩存策略的配 置,只對集合數據索引進行了緩存,而並沒有對集合中的實體對象進行緩存,所以在第二次再次加載實體時,Hibernate找到了對應實體的數據索引,但是 根據數據索引,卻無法在緩存中找到對應的實體,所以Hibernate根據找到的數據索引發起了兩條select SQL的查詢操作,這裏造成了對性能的浪費,怎樣才能避免這種情況呢?我們必須對集合類型中的實體也指定緩存策略,所以我們要如下對集合類型進行配置:

  1. <Hibernate-mapping>      
  2. <class  name =”com.neusoft.entity. User ”  table =” user ”>…..  
  3. < set   name =”addresses”  table =”address” lazy=” true ”   
  4. inverse=” true ”><cache usage=” read -write”/>  
  5. < key   column =”user_id”/>  
  6. <one- to -many class=”com.neusoft.entity.Arrderss”/></ set >      
  7. </class></Hibernate-mapping> 

此時Hibernate會對集合類型中的實體也進行緩存,如果根據這個配置再次運行上面的代碼,將會得到類似如下的輸出:

  1. Select  *  from   user   where  id=’1’;  
  2. Select  *  from  address  where  user_id=’1’;  
  3. TianjinDalianSecond query……TianjinDalian 

這時將不會再有根據數據索引進行查詢的SQL語句,因爲此時可以直接從緩存中獲得集合類型中存放的實體對象。

C、Hibernate延遲加載之屬性延遲加載:

在Hibernate3中,引入了一種新的特性——屬性的延遲加載,這個機制又爲獲取高性能查詢提供了有力的工具。在前面我們講大數據對象讀取時, 在User對象中有一個resume字段,該字段是一個java.sql.Clob類型,包含了用戶的簡歷信息,當我們加載該對象時,我們不得不每一次都 要加載這個字段,而不論我們是否真的需要它,而且這種大數據對象的讀取本身會帶來很大的性能開銷。在Hibernate2中,我們只有通過我們前面講過的 面性能的粒度細分,來分解User類,來解決這個問題(請參照那一節的論述),但是在Hibernate3中,我們可以通過屬性延遲加載機制,來使我們獲 得只有當我們真正需要操作這個字段時,纔去讀取這個字段數據的能力,爲此我們必須如下配置我們的實體類:

  1. <Hibernate-mapping>  
  2. <class  name =”com.neusoft.entity. User ”  table =” user ”>……  
  3. <property  name =”resume” type=”java.sql.Clob”   
  4. column =”resume” lazy=” true ”/>      
  5. </class></Hibernate-mapping> 

通過對<property>元素的lazy屬性設置true來開啓屬性的延遲加載,在Hibernate3中爲了實現屬性的延遲加載, 使用了類增強器來對實體類的Class文件進行強化處理,通過增強器的增強,將CGLIB的回調機制邏輯,加入實體類,這裏我們可以看出屬性的延遲加載, 還是通過CGLIB來實現的。CGLIB是Apache的一個開源工程,這個類庫可以操縱java類的字節碼,根據字節碼來動態構造符合要求的類對象。根 據上面的配置我們運行下面的代碼:

  1. String sql=” from   User   user   where   user . name =’zx’ ”;  
  2. Query query=session.createQuery(sql);    (1)  
  3. List list=query.list();  
  4. for ( int  i=0;i<list. size ();i++)  
  5. { User   user =( User )list.get(i);  
  6. System. out .println( user .getName());  
  7. System. out .println( user .getResume());    (2)  

當執行到(1)處時,會生成類似如下的SQL語句:

  1. Select  id,age, name   from   user   where   name =’zx’; 

這時Hibernate會檢索User實體中所有非延遲加載屬性對應的字段數據,當執行到(2)處時,會生成類似如下的SQL語句:

  1. Select  resume  from   user   where  id=’1’; 

這時會發起對resume字段數據真正的讀取操作。

 

本文摘自:http://developer.51cto.com/art/200906/129710.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章