欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Java的Hibernate框架中的緩存和延遲加載機制

 更新時間:2015年11月20日 08:54:58   作者:stevenjohn  
這篇文章主要介紹了Java的Hibernate框架中的緩存和延遲加載機制,Hibernate是注明的Java下SSH三大web開發(fā)框架之一,需要的朋友可以參考下

hibernate一級緩存和二級緩存的區(qū)別
緩存是介于應(yīng)用程序和物理數(shù)據(jù)源之間,其作用是為了降低應(yīng)用程序?qū)ξ锢頂?shù)據(jù)源訪問的頻次,從而提高了應(yīng)用的運行性能。緩存內(nèi)的數(shù)據(jù)是對物理數(shù)據(jù)源中的數(shù)據(jù)的復(fù)制,應(yīng)用程序在運行時從緩存讀寫數(shù)據(jù),在特定的時刻或事件會同步緩存和物理數(shù)據(jù)源的數(shù)據(jù)。
  緩存的介質(zhì)一般是內(nèi)存,所以讀寫速度很快。但如果緩存中存放的數(shù)據(jù)量非常大時,也會用硬盤作為緩存介質(zhì)。緩存的實現(xiàn)不僅僅要考慮存儲的介質(zhì),還要考慮到管理緩存的并發(fā)訪問和緩存數(shù)據(jù)的生命周期。
  Hibernate的緩存包括Session的緩存和SessionFactory的緩存,其中SessionFactory的緩存又可以分為兩類:內(nèi)置緩存和外置緩存。Session的緩存是內(nèi)置的,不能被卸載,也被稱為Hibernate的第一級緩存。SessionFactory的內(nèi)置緩存和Session的緩存在實現(xiàn)方式上比較相似,前者是SessionFactory對象的一些集合屬性包含的數(shù)據(jù),后者是指Session的一些集合屬性包含的數(shù)據(jù)。SessionFactory的內(nèi)置緩存中存放了映射元數(shù)據(jù)和預(yù)定義SQL語句,映射元數(shù)據(jù)是映射文件中數(shù)據(jù)的拷貝,而預(yù)定義SQL語句是在Hibernate初始化階段根據(jù)映射元數(shù)據(jù)推導(dǎo)出來,SessionFactory的內(nèi)置緩存是只讀的,應(yīng)用程序不能修改緩存中的映射元數(shù)據(jù)和預(yù)定義SQL語句,因此SessionFactory不需要進行內(nèi)置緩存與映射文件的同步。SessionFactory的外置緩存是一個可配置的插件。在默認情況下,SessionFactory不會啟用這個插件。外置緩存的數(shù)據(jù)是數(shù)據(jù)庫數(shù)據(jù)的拷貝,外置緩存的介質(zhì)可以是內(nèi)存或者硬盤。SessionFactory的外置緩存也被稱為Hibernate的第二級緩存。
  Hibernate的這兩級緩存都位于持久化層,存放的都是數(shù)據(jù)庫數(shù)據(jù)的拷貝,那么它們之間的區(qū)別是什么呢?為了理解二者的區(qū)別,需要深入理解持久化層的緩存的兩個特性:緩存的范圍和緩存的并發(fā)訪問策略。
  持久化層的緩存的范圍
  緩存的范圍決定了緩存的生命周期以及可以被誰訪問。緩存的范圍分為三類。
  1 事務(wù)范圍:緩存只能被當前事務(wù)訪問。緩存的生命周期依賴于事務(wù)的生命周期,當事務(wù)結(jié)束時,緩存也就結(jié)束生命周期。在此范圍下,緩存的介質(zhì)是內(nèi)存。事務(wù)可以是數(shù)據(jù)庫事務(wù)或者應(yīng)用事務(wù),每個事務(wù)都有獨自的緩存,緩存內(nèi)的數(shù)據(jù)通常采用相互關(guān)聯(lián)的的對象形式。
  2 進程范圍:緩存被進程內(nèi)的所有事務(wù)共享。這些事務(wù)有可能是并發(fā)訪問緩存,因此必須對緩存采取必要的事務(wù)隔離機制。緩存的生命周期依賴于進程的生命周期,進程結(jié)束時,緩存也就結(jié)束了生命周期。進程范圍的緩存可能會存放大量的數(shù)據(jù),所以存放的介質(zhì)可以是內(nèi)存或硬盤。緩存內(nèi)的數(shù)據(jù)既可以是相互關(guān)聯(lián)的對象形式也可以是對象的松散數(shù)據(jù)形式。松散的對象數(shù)據(jù)形式有點類似于對象的序列化數(shù)據(jù),但是對象分解為松散的算法比對象序列化的算法要求更快。
  3 集群范圍:在集群環(huán)境中,緩存被一個機器或者多個機器的進程共享。緩存中的數(shù)據(jù)被復(fù)制到集群環(huán)境中的每個進程節(jié)點,進程間通過遠程通信來保證緩存中的數(shù)據(jù)的一致性,緩存中的數(shù)據(jù)通常采用對象的松散數(shù)據(jù)形式。
  對大多數(shù)應(yīng)用來說,應(yīng)該慎重地考慮是否需要使用集群范圍的緩存,因為訪問的速度不一定會比直接訪問數(shù)據(jù)庫數(shù)據(jù)的速度快多少。
  持久化層可以提供多種范圍的緩存。如果在事務(wù)范圍的緩存中沒有查到相應(yīng)的數(shù)據(jù),還可以到進程范圍或集群范圍的緩存內(nèi)查詢,如果還是沒有查到,那么只有到數(shù)據(jù)庫中查詢。事務(wù)范圍的緩存是持久化層的第一級緩存,通常它是必需的;進程范圍或集群范圍的緩存是持久化層的第二級緩存,通常是可選的。
  持久化層的緩存的并發(fā)訪問策略
  當多個并發(fā)的事務(wù)同時訪問持久化層的緩存的相同數(shù)據(jù)時,會引起并發(fā)問題,必須采用必要的事務(wù)隔離措施。
  在進程范圍或集群范圍的緩存,即第二級緩存,會出現(xiàn)并發(fā)問題。因此可以設(shè)定以下四種類型的并發(fā)訪問策略,每一種策略對應(yīng)一種事務(wù)隔離級別。
  事務(wù)型:僅僅在受管理環(huán)境中適用。它提供了Repeatable Read事務(wù)隔離級別。對于經(jīng)常被讀但很少修改的數(shù)據(jù),可以采用這種隔離類型,因為它可以防止臟讀和不可重復(fù)讀這類的并發(fā)問題。
  讀寫型:提供了Read Committed事務(wù)隔離級別。僅僅在非集群的環(huán)境中適用。對于經(jīng)常被讀但很少修改的數(shù)據(jù),可以采用這種隔離類型,因為它可以防止臟讀這類的并發(fā)問題。
  非嚴格讀寫型:不保證緩存與數(shù)據(jù)庫中數(shù)據(jù)的一致性。如果存在兩個事務(wù)同時訪問緩存中相同數(shù)據(jù)的可能,必須為該數(shù)據(jù)配置一個很短的數(shù)據(jù)過期時間,從而盡量避免臟讀。對于極少被修改,并且允許偶爾臟讀的數(shù)據(jù),可以采用這種并發(fā)訪問策略。   只讀型:對于從來不會修改的數(shù)據(jù),如參考數(shù)據(jù),可以使用這種并發(fā)訪問策略。
  事務(wù)型并發(fā)訪問策略是事務(wù)隔離級別最高,只讀型的隔離級別最低。事務(wù)隔離級別越高,并發(fā)性能就越低。
  什么樣的數(shù)據(jù)適合存放到第二級緩存中?
  1、很少被修改的數(shù)據(jù)
  2、不是很重要的數(shù)據(jù),允許出現(xiàn)偶爾并發(fā)的數(shù)據(jù)
  3、不會被并發(fā)訪問的數(shù)據(jù)
  4、參考數(shù)據(jù)
  不適合存放到第二級緩存的數(shù)據(jù)?
  1、經(jīng)常被修改的數(shù)據(jù)
  2、財務(wù)數(shù)據(jù),絕對不允許出現(xiàn)并發(fā)
  3、與其他應(yīng)用共享的數(shù)據(jù)。
  Hibernate的二級緩存
  如前所述,Hibernate提供了兩級緩存,第一級是Session的緩存。由于Session對象的生命周期通常對應(yīng)一個數(shù)據(jù)庫事務(wù)或者一個應(yīng)用事務(wù),因此它的緩存是事務(wù)范圍的緩存。第一級緩存是必需的,不允許而且事實上也無法比卸除。在第一級緩存中,持久化類的每個實例都具有唯一的OID。
  第二級緩存是一個可插拔的的緩存插件,它是由SessionFactory負責管理。由于SessionFactory對象的生命周期和應(yīng)用程序的整個過程對應(yīng),因此第二級緩存是進程范圍或者集群范圍的緩存。這個緩存中存放的對象的松散數(shù)據(jù)。第二級對象有可能出現(xiàn)并發(fā)問題,因此需要采用適當?shù)牟l(fā)訪問策略,該策略為被緩存的數(shù)據(jù)提供了事務(wù)隔離級別。緩存適配器用于把具體的緩存實現(xiàn)軟件與Hibernate集成。第二級緩存是可選的,可以在每個類或每個集合的粒度上配置第二級緩存。
  Hibernate的二級緩存策略的一般過程如下:
  1) 條件查詢的時候,總是發(fā)出一條select * from table_name where …. (選擇所有字段)這樣的SQL語句查詢數(shù)據(jù)庫,一次獲得所有的數(shù)據(jù)對象。
  2) 把獲得的所有數(shù)據(jù)對象根據(jù)ID放入到第二級緩存中。
  3) 當Hibernate根據(jù)ID訪問數(shù)據(jù)對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那么從二級緩存中查;查不到,再查詢數(shù)據(jù)庫,把結(jié)果按照ID放入到緩存。
  4) 刪除、更新、增加數(shù)據(jù)的時候,同時更新緩存。
  Hibernate的二級緩存策略,是針對于ID查詢的緩存策略,對于條件查詢則毫無作用。為此,Hibernate提供了針對條件查詢的Query緩存。
  Hibernate的Query緩存策略的過程如下:
  1) Hibernate首先根據(jù)這些信息組成一個Query Key,Query Key包括條件查詢的請求一般信息:SQL, SQL需要的參數(shù),記錄范圍(起始位置rowStart,最大記錄個數(shù)maxRows),等。
  2) Hibernate根據(jù)這個Query Key到Query緩存中查找對應(yīng)的結(jié)果列表。如果存在,那么返回這個結(jié)果列表;如果不存在,查詢數(shù)據(jù)庫,獲取結(jié)果列表,把整個結(jié)果列表根據(jù)Query Key放入到Query緩存中。
  3) Query Key中的SQL涉及到一些表名,如果這些表的任何數(shù)據(jù)發(fā)生修改、刪除、增加等操作,這些相關(guān)的Query Key都要從緩存中清空。

Hibernate延遲加載機制
延遲加載:

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

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

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

<hibernate-mapping> 

<class name=”com.neusoft.entity.User” table=”user” lazy=”true”> 

  …… 

</class> 

</hibernate-mapping> 

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

User user=(User)session.load(User.class,”1”); 

(1)

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

(2)

當運行到(1)處時,Hibernate并沒有發(fā)起對數(shù)據(jù)的查詢,如果我們此時通過一些調(diào)試工具(比如JBuilder2005的Debug工具),觀察此時user對象的內(nèi)存快照,我們會驚奇的發(fā)現(xiàn),此時返回的可能是User$EnhancerByCGLIB$$bede8986類型的對象,而且其屬性為null,這是怎么回事?還記得前面我曾講過session.load()方法,會返回實體對象的代理類對象,這里所返回的對象類型就是User對象的代理類對象。在Hibernate中通過使用CGLIB,來實現(xiàn)動態(tài)構(gòu)造一個目標對象的代理類對象,并且在代理類對象中包含目標對象的所有屬性和方法,而且所有屬性均被賦值為null。通過調(diào)試器顯示的內(nèi)存快照,我們可以看出此時真正的User對象,是包含在代理對象的CGLIB$CALBACK_0.target屬性中,當代碼運行到(2)處時,此時調(diào)用user.getName()方法,這時通過CGLIB賦予的回調(diào)機制,實際上調(diào)用CGLIB$CALBACK_0.getName()方法,當調(diào)用該方法時,Hibernate會首先檢查CGLIB$CALBACK_0.target屬性是否為null,如果不為空,則調(diào)用目標對象的getName方法,如果為空,則會發(fā)起數(shù)據(jù)庫查詢,生成類似這樣的SQL語句:select * from user where id='1';來查詢數(shù)據(jù),并構(gòu)造目標對象,并且將它賦值到CGLIB$CALBACK_0.target屬性中。

    這樣,通過一個中間代理對象,Hibernate實現(xiàn)了實體的延遲加載,只有當用戶真正發(fā)起獲得實體對象屬性的動作時,才真正會發(fā)起數(shù)據(jù)庫查詢操作。所以實體的延遲加載是用通過中間代理類完成的,所以只有session.load()方法才會利用實體延遲加載,因為只有session.load()方法才會返回實體類的代理類對象。

B、        集合類型的延遲加載:

在Hibernate的延遲加載機制中,針對集合類型的應(yīng)用,意義是最為重大的,因為這有可能使性能得到大幅度的提高,為此Hibernate進行了大量的努力,其中包括對JDK Collection的獨立實現(xiàn),我們在一對多關(guān)聯(lián)中,定義的用來容納關(guān)聯(lián)對象的Set集合,并不是java.util.Set類型或其子類型,而是net.sf.hibernate.collection.Set類型,通過使用自定義集合類的實現(xiàn),Hibernate實現(xiàn)了集合類型的延遲加載。為了對集合類型使用延遲加載,我們必須如下配置我們的實體類的關(guān)于關(guān)聯(lián)的部分:

<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屬性設(shè)置為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()); 

} 

當程序執(zhí)行到(1)處時,這時并不會發(fā)起對關(guān)聯(lián)數(shù)據(jù)的查詢來加載關(guān)聯(lián)數(shù)據(jù),只有運行到(2)處時,真正的數(shù)據(jù)讀取操作才會開始,這時Hibernate會根據(jù)緩存中符合條件的數(shù)據(jù)索引,來查找符合條件的實體對象。

這里我們引入了一個全新的概念——數(shù)據(jù)索引,下面我們首先將接一下什么是數(shù)據(jù)索引。在Hibernate中對集合類型進行緩存時,是分兩部分進行緩存的,首先緩存集合中所有實體的id列表,然后緩存實體對象,這些實體對象的id列表,就是所謂的數(shù)據(jù)索引。當查找數(shù)據(jù)索引時,如果沒有找到對應(yīng)的數(shù)據(jù)索引,這時就會一條select SQL的執(zhí)行,獲得符合條件的數(shù)據(jù),并構(gòu)造實體對象集合和數(shù)據(jù)索引,然后返回實體對象的集合,并且將實體對象和數(shù)據(jù)索引納入Hibernate的緩存之中。另一方面,如果找到對應(yīng)的數(shù)據(jù)索引,則從數(shù)據(jù)索引中取出id列表,然后根據(jù)id在緩存中查找對應(yīng)的實體,如果找到就從緩存中返回,如果沒有找到,在發(fā)起select SQL查詢。在這里我們看出了另外一個問題,這個問題可能會對性能產(chǎn)生影響,這就是集合類型的緩存策略。如果我們?nèi)缦屡渲眉项愋停?/p>

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

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

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 

我們看到,當?shù)诙螆?zhí)行查詢時,執(zhí)行了兩條對address表的查詢操作,為什么會這樣?這是因為當?shù)谝淮渭虞d實體后,根據(jù)集合類型緩存策略的配置,只對集合數(shù)據(jù)索引進行了緩存,而并沒有對集合中的實體對象進行緩存,所以在第二次再次加載實體時,Hibernate找到了對應(yīng)實體的數(shù)據(jù)索引,但是根據(jù)數(shù)據(jù)索引,卻無法在緩存中找到對應(yīng)的實體,所以Hibernate根據(jù)找到的數(shù)據(jù)索引發(fā)起了兩條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會對集合類型中的實體也進行緩存,如果根據(jù)這個配置再次運行上面的代碼,將會得到類似如下的輸出:

Select * from user where id='1'; 

Select * from address where user_id='1'; 

Tianjin 

Dalian 

Second query…… 

Tianjin 

Dalian 

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

C、       屬性延遲加載:

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

<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屬性設(shè)置true來開啟屬性的延遲加載,在Hibernate3中為了實現(xiàn)屬性的延遲加載,使用了類增強器來對實體類的Class文件進行強化處理,通過增強器的增強,將CGLIB的回調(diào)機制邏輯,加入實體類,這里我們可以看出屬性的延遲加載,還是通過CGLIB來實現(xiàn)的。CGLIB是Apache的一個開源工程,這個類庫可以操縱java類的字節(jié)碼,根據(jù)字節(jié)碼來動態(tài)構(gòu)造符合要求的類對象。根據(jù)上面的配置我們運行下面的代碼:

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)

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

Select id,age,name from user where name='zx'; 

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

Select resume from user where id='1'; 

這時會發(fā)起對resume字段數(shù)據(jù)真正的讀取操作。

相關(guān)文章

最新評論