天天看點

Hibernate延遲加載機制詳解

摘自 http://blog.chinaunix.net/uid-20577907-id-3129234.html

<b>延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要資料的時候,才真正執行資料加載操作。</b>

在Hibernate中提供了對實體對象的延遲加載以及對集合的延遲加載,另外在Hibernate3中還提供了對屬性的延遲加載。下面我們就分别介紹這些種類的延遲加載的細節。

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

&lt;hibernate-mapping&gt;

&lt;<b>class </b>name=”com.neusoft.entity.User”

table=”user” <b>lazy=”true”</b>&gt;

……

&lt;/class&gt;

&lt;/hibernate-mapping&gt;

通過将class的lazy屬性設定為true,來開啟實體的延遲加載特性。如果我們運作下面的代碼:

User

user=(User)<b>session.load</b>(User.class,”1”);(1)

System.out.println(user.getName());(2)

當運作到(1)處時,Hibernate并沒有發起對資料的查詢,如果我們此時通過一些調試工具(比如JBuilder2005的Debug工具),觀察此時user對象的記憶體快照,我們會驚奇的發現,此時傳回的可能是User$EnhancerByCGLIB$$bede8986類型的對象,而且其屬性為null,這是怎麼回事?還記得前面我曾講過session.load()方法,會傳回實體對象的代理類對象,這裡所傳回的對象類型就是User對象的<b>代理類對象</b>。在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屬性中。

<b>這樣,通過一個中間代理對象,Hibernate實作了實體的延遲加載,隻有當使用者真正發起獲得實體對象屬性的動作時,才真正會發起資料庫查詢操作。是以實體的延遲加載是用通過中間代理類完成的,是以隻有session.load()方法才會利用實體延遲加載,因為隻有session.load()方法才會傳回實體類的代理類對象。</b>

&lt;class&gt;标簽上的lazy隻影響到該對象本身的普通屬性。對其它(集合)屬性不影響.

<b></b>

在Hibernate的延遲加載機制中,<b>針對集合類型的應用,意義是最為重大的</b>,因為這有可能使性能得到大幅度的提高,為此Hibernate進行了大量的努力,其中包括對JDK

Collection的獨立實作,我們在一對多關聯中,定義的用來容納關聯對象的Set集合,并不是java.util.Set類型或其子類型,而是net.sf.hibernate.collection.Set類型,通過使用自定義集合類的實作,Hibernate實作了集合類型的延遲加載。為了對集合類型使用延遲加載,我們必須如下配置我們的實體類的關于關聯的部分:

&lt;class name=”com.neusoft.entity.User”

table=”user”&gt;

…..

&lt;set name=”addresses” table=”address” lazy=”true” inverse=”true”&gt;

&lt;key column=”user_id”/&gt;

&lt;one-to-many

class=”com.neusoft.entity.Arrderss”/&gt;

&lt;/set&gt;

通過将&lt;set&gt;元素的lazy屬性設定為true來開啟集合類型的延遲加載特性。我們看下面的代碼:

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會根據緩存中符合條件的資料索引,來查找符合條件的實體對象。

這裡我們引入了一個全新的概念——資料索引,下面我們首先将接一下<b>什麼是資料索引</b>。

在Hibernate中對集合類型進行緩存時,是分兩部分進行緩存的,<b>1首先緩存集合中所有實體的id清單,2然後緩存實體對象</b>,這些實體對象的id清單,就是所謂的資料索引。

當查找資料索引時,如果沒有找到對應的資料索引,這時就會一條select

SQL的執行,獲得符合條件的資料,并構造實體對象集合和資料索引,然後傳回實體對象的集合,并且将實體對象和資料索引納入Hibernate的緩存之中。

另一方面,如果找到對應的資料索引,則從資料索引中取出id清單,然後根據id在緩存中查找對應的實體,如果找到就從緩存中傳回,如果沒有找到,在發起select

SQL查詢。

在這裡我們看出了另外一個問題,這個問題可能會對性能産生影響,這就是集合類型的緩存政策。如果我們如下配置集合類型:

&lt;set name=”addresses” table=”address”

lazy=”true” inverse=”true”&gt;

&lt;cache

usage=”read-only”/&gt;

這裡我們應用了&lt;cache

usage=”read-only”/&gt;配置,如果采用這種政策來配置集合類型,Hibernate将隻會對資料索引進行緩存,而不會對集合中的實體對象進行緩存。如上配置我們運作下面的代碼:

Iterator it=addset.iterator();

System.out.println(“Second query……”);

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’;

我們看到,當第二次執行查詢時,執行了兩條對address表的查詢操作,為什麼會這樣?這是因為當第一次加載實體後,根據集合類型緩存政策的配置(即上文中的&lt;cache

usage=”read-only”/&gt;),隻對集合資料索引進行了緩存,而并沒有對集合中的實體對象進行緩存。是以在第二次再次加載實體時,Hibernate找到了對應實體的資料索引,但是根據資料索引,卻無法在緩存中找到對應的實體,是以Hibernate根據找到的資料索引發起了兩條select

SQL的查詢操作,這裡造成了對性能的浪費。

怎樣才能避免這種情況呢?我們必須對集合類型中的實體也指定緩存政策,是以我們要如下對集合類型進行配置:

&lt;cache usage=”read-write”/&gt;

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

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

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

&lt;property

name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/&gt;

通過對&lt;property&gt;元素的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&lt;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字段資料真正的讀取操作。

**

一般的:

hibernate 一級緩存:存儲實體對象

hibernate 二級緩存:存儲ID(資料索引)

用戶端觸發查詢時,先去二級緩存中按ID查詢,若查詢到對應的ID,再到一級緩存中讀取對應的實體對象;若查詢不到ID,則直接去資料庫中查詢,查詢完将ID放到一級緩存,查詢到的實體對象放入二級緩存。

X

選擇其他平台 &gt;&gt;

分享到

更多平台... (133)