hibernate二級(jí)緩存
與Session 相對(duì)的是,SessionFactory 也提供了相應(yīng)的緩存機(jī)制。SessionFactory 緩存可以依據(jù)功能和目的的不同而劃分為內(nèi)置緩存和外置緩存。 SessionF
與Session 相對(duì)的是,SessionFactory 也提供了相應(yīng)的緩存機(jī)制。SessionFactory 緩存可以依據(jù)功能和目的的不同而劃分為內(nèi)置緩存和外置緩存。 SessionFactory 的內(nèi)置緩存中存放了映射元數(shù)據(jù)和預(yù)定義SQL 語(yǔ)句,映射元數(shù)據(jù)是映射文件中數(shù)據(jù)的副本,而預(yù)定義SQL 語(yǔ)句是在Hibernate 初始化階段根據(jù)映射元數(shù)據(jù)推導(dǎo)出來(lái)的。SessionFactory 的內(nèi)置緩存是只讀的,應(yīng)用程序不能修改緩存中的映射元數(shù)據(jù)和預(yù)定義SQL 語(yǔ)句,因此SessionFactory 不需要進(jìn)行內(nèi)置緩存與映射文件的同步。
SessionFactory 的外置緩存是一個(gè)可配置的插件。在默認(rèn)情況下,SessionFactory 不會(huì)啟用這個(gè)插件。外置緩存的數(shù)據(jù)是數(shù)據(jù)庫(kù)數(shù)據(jù)的副本,外置緩存的介質(zhì)可以是內(nèi)存或者硬盤。SessionFactory 的外置緩存也被稱為Hibernate 的二級(jí)緩存。
Hibernate 的二級(jí)緩存的實(shí)現(xiàn)原理與一級(jí)緩存是一樣的,也是通過(guò)以ID 為key 的Map 來(lái)實(shí)現(xiàn)對(duì)對(duì)象的緩存。
由于Hibernate 的二級(jí)緩存是作用在SessionFactory 范圍內(nèi)的,因而它比一級(jí)緩存的范圍更廣,可以被所有的Session 對(duì)象所共享。
14.2.3.1 二級(jí)緩存的工作內(nèi)容
Hibernate 的二級(jí)緩存同一級(jí)緩存一樣,也是針對(duì)對(duì)象ID 來(lái)進(jìn)行緩存。所以說(shuō),二級(jí)緩存的作用范圍是針對(duì)根據(jù)ID 獲得對(duì)象的查詢。
二級(jí)緩存的工作可以概括為以下幾個(gè)部分:
● 在執(zhí)行各種條件查詢時(shí),如果所獲得的結(jié)果集為實(shí)體對(duì)象的集合,那么就會(huì)把所有的數(shù)據(jù)對(duì)象根據(jù)ID 放入到二級(jí)緩存中。
● 當(dāng)Hibernate 根據(jù)ID 訪問(wèn)數(shù)據(jù)對(duì)象的時(shí)候,首先會(huì)從Session 一級(jí)緩存中查找,如果查不到并且配置了二級(jí)緩存,那么會(huì)從二級(jí)緩存中查找,如果還查不到,就會(huì)查詢數(shù)據(jù)庫(kù),把結(jié)果按照ID 放入到緩存中。
● 刪除、更新、增加數(shù)據(jù)的時(shí)候,同時(shí)更新緩存。
14.2.3.2 二級(jí)緩存的適用范圍
Hibernate 的二級(jí)緩存作為一個(gè)可插入的組件在使用的時(shí)候也是可以進(jìn)行配置的,但并不是所有的對(duì)象都適合放在二級(jí)緩存中。
在通常情況下會(huì)將具有以下特征的數(shù)據(jù)放入到二級(jí)緩存中:
● 很少被修改的數(shù)據(jù)。
● 不是很重要的數(shù)據(jù),允許出現(xiàn)偶爾并發(fā)的數(shù)據(jù)。
● 不會(huì)被并發(fā)訪問(wèn)的數(shù)據(jù)。
● 參考數(shù)據(jù)。
而對(duì)于具有以下特征的數(shù)據(jù)則不適合放在二級(jí)緩存中:
● 經(jīng)常被修改的數(shù)據(jù)。
● 財(cái)務(wù)數(shù)據(jù),絕對(duì)不允許出現(xiàn)并發(fā)。
● 與其他應(yīng)用共享的數(shù)據(jù)。
在這里特別要注意的是對(duì)放入緩存中的數(shù)據(jù)不能有第三方的應(yīng)用對(duì)數(shù)據(jù)進(jìn)行更改(其中也包括在自己程序中使用其他方式進(jìn)行數(shù)據(jù)的修改,例如,JDBC ),因?yàn)槟菢親ibernate 將不會(huì)知道數(shù)據(jù)已經(jīng)被修改,也就無(wú)法保證緩存中的數(shù)據(jù)與數(shù)據(jù)庫(kù)中數(shù)據(jù)的一致性。
14.2.3.3 二級(jí)緩存組件
在默認(rèn)情況下,Hibernate 會(huì)使用EHCache 作為二級(jí)緩存組件。但是,可以通過(guò)設(shè)置hibernate.cache.provider_class屬性,指定其他的緩存策略,該緩存策略
,必須實(shí)現(xiàn)org.hibernate.cache.CacheProvider 接口。
通過(guò)實(shí)現(xiàn)org.hibernate.cache.CacheProvider 接口可以提供對(duì)不同二級(jí)緩存組件的支持。
Hibernate 內(nèi)置支持的二級(jí)緩存組件如表14.1所示。
14.2.3.4 二級(jí)緩存的配置
在使用Hibernate 的二級(jí)緩存時(shí),對(duì)于每個(gè)需要使用二級(jí)緩存的對(duì)象都需要進(jìn)行相應(yīng)的配置工作。也就是說(shuō),只有配置了使用二級(jí)緩存的對(duì)象才會(huì)被放置在二級(jí)緩存中。二級(jí)緩存是通過(guò)
usage="transactional|read-write|nonstrict-read-write|read-only" (1) region="RegionName" (2) include="all|non-lazy" (3) /> 當(dāng)多個(gè)并發(fā)的事務(wù)同時(shí)訪問(wèn)持久化層的緩存中的相同數(shù)據(jù)時(shí),會(huì)引起并發(fā)問(wèn) 題,必須采用必要的事務(wù)隔離措施。 在進(jìn)程范圍或集群范圍的緩存,即第二級(jí)緩存,會(huì)出現(xiàn)并發(fā)問(wèn)題。因此可以設(shè)定以下4種類型的并發(fā)訪問(wèn)策略,每一種策略對(duì)應(yīng)一種事務(wù)隔離級(jí)別。 ● 只讀緩存(read-only ) 如果應(yīng)用程序需要讀取一個(gè)持久化類的實(shí)例,但是并不打算修改它們,可以使用read-only 緩存。這是最簡(jiǎn)單,也是實(shí)用性最好的策略。 對(duì)于從來(lái)不會(huì)修改的數(shù)據(jù),如參考數(shù)據(jù),可以使用這種并發(fā)訪問(wèn)策略。 ● 讀/寫(xiě)緩存(read-write ) 如果應(yīng)用程序需要更新數(shù)據(jù),可能read-write 緩存比較合適。如果需要序列化事務(wù)隔離級(jí)別,那么就不能使用這種緩存策略。 對(duì)于經(jīng)常被讀但很少修改的數(shù)據(jù),可以采用這種隔離類型,因?yàn)樗梢苑乐古K讀這類的并發(fā)問(wèn)題。 ● 不嚴(yán)格的讀/寫(xiě)緩存(nonstrict-read-write ) 如果程序偶爾需要更新數(shù)據(jù)(也就是說(shuō),出現(xiàn)兩個(gè)事務(wù)同時(shí)更新同一個(gè)條目的現(xiàn)象很不常見(jiàn)),也不需要十分嚴(yán)格的事務(wù)隔離,可能適用nonstrict-read-write 緩存。 對(duì)于極少被修改,并且允許偶爾臟讀的數(shù)據(jù),可以采用這種并發(fā)訪問(wèn)策略。 ● 事務(wù)緩存(transactional ) transactional 緩存策略提供了對(duì)全事務(wù)的緩存,僅僅在受管理環(huán)境中使用。它提供了Repeatable Read 事務(wù)隔離級(jí)別。對(duì)于經(jīng)常被讀但很少修改的數(shù)據(jù),可以采用這種隔離類型,因?yàn)樗梢苑乐古K讀和不可重復(fù)讀這類的并發(fā)問(wèn)題。 在上面所介紹的隔離級(jí)別中,事務(wù)型并發(fā)訪問(wèn)策略的隔離級(jí)別最高,然后依次是讀/寫(xiě)型和不嚴(yán)格讀寫(xiě)型,只讀型的隔離級(jí)別最低。事務(wù)的隔離級(jí)別越高,并發(fā)性能就越低。 14.2.3.6 在開(kāi)發(fā)中使用二級(jí)緩存 在這一部分中,將細(xì)致地介紹如何在Hibernate 中使用二級(jí)緩存。在這里所使用的二級(jí)緩存組件為EHCache 。 關(guān)于EHCache 的詳細(xì)信息請(qǐng)參考http://ehcache.sourceforge.net上的內(nèi)容。 在Hibernate 中使用二級(jí)緩存需要經(jīng)歷以下步驟: ● 在Hibernate 配置文件(通常為hibernate.cfg.xml )中,設(shè)置二級(jí)緩存的提供者類。 ● 配置EHCache 的基本參數(shù)。 ● 在需要進(jìn)行緩存的實(shí)體對(duì)象的映射文件中配置緩存的策略。 下面就來(lái)逐步演示一下如何在開(kāi)發(fā)中使用Hibernate 的二級(jí)緩存。 修改Hibernate 的配置文件 在使用Hibernate 的二級(jí)緩存時(shí),需要在Hibernate 的配置文件中指定緩存提供者對(duì)象,以便于Hibernate 可以通過(guò)其實(shí)現(xiàn)對(duì)數(shù)據(jù)的緩存處理。 在這里需要設(shè)置的參數(shù)是hibernate.cache.provider_class,在使用EHCache 時(shí),需要將其值設(shè)置為org.hibernate.cache.EhCacheProvider 。具體要增加的配置如下所示: org.hibernate.cache.EhCacheProvider Hibernate 配置文件的詳細(xì)內(nèi)容請(qǐng)參考配套光盤中的hibernatesrccnhxex hibernate?chehibernate.cfg.xml文件。 增加EHCache 配置參數(shù) 在默認(rèn)情況下,EHCache 會(huì)到classpath 所指定的路徑中尋找ehcache.xml 文件來(lái)作為EHCache 的配置文件。 在配置文件中,包含了EHCache 進(jìn)行緩存管理時(shí)的一些基本的參數(shù)。具體的配置方法如清單14.9所示。 清單14.9 EHCache 的配置 xsi:noNamespaceSchemaLocation="ehcache.xsd"> maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> 在這里只是使用EHCache 所提供的默認(rèn)配置文件進(jìn)行了EHCache 的基本配置,對(duì)于這些參數(shù)的詳細(xì)含義請(qǐng)參考其官方網(wǎng)站(http://ehcache.sourceforge.net/)中的資料。在實(shí)際的開(kāi)發(fā)中,應(yīng)該依據(jù)自己的具體情況來(lái)設(shè)置這些參數(shù)的值。 開(kāi)發(fā)實(shí)體對(duì)象 這里所使用的是一個(gè)非常簡(jiǎn)單的User 對(duì)象,它只包含了ID ,name 和age 三個(gè)屬性,具體的實(shí)現(xiàn)方法請(qǐng)參見(jiàn)配套光盤中的hibernatesrccnhxex?cheUser.java文件。 配置映射文件 映射文件的配置與不使用二級(jí)緩存的Java 對(duì)象的區(qū)別就在于需要增加前面所介紹的 映射文件的詳細(xì)配置請(qǐng)參考配套光盤中的hibernatesrccnhxex?cheUser.hbm.xml文件。 測(cè)試主程序 在這里的測(cè)試主程序采用了多線程的運(yùn)行方式,以模擬在不同Session 的情況下是否真的可以避免查詢的重復(fù)進(jìn)行。 在這個(gè)測(cè)試程序中,所做的工作就是依據(jù)ID 來(lái)得到對(duì)應(yīng)的實(shí)體對(duì)象,并將其輸出。然后通過(guò)多次運(yùn)行此程序,來(lái)檢查后面的運(yùn)行是否進(jìn)行了數(shù)據(jù)庫(kù)的操作。 測(cè)試主程序的主要測(cè)試方法的實(shí)現(xiàn)如清單14.10所示。 清單14.10 測(cè)試主程序的實(shí)現(xiàn) …… public void run() { SessionFactory sf = CacheMain.getSessionFactory(); Session session = sf.getCurrentSession(); session.beginTransaction(); User user = (User)session.get( User.class, "1" ); System.out.println( user ); session.getTransaction().commit(); } public static void main(String[] args) { CacheMain main1 = new CacheMain(); main1.start(); CacheMain main2 = new CacheMain(); main2.start(); } } 運(yùn)行測(cè)試程序 在運(yùn)行測(cè)試程序之前,需要先手動(dòng)地向數(shù)據(jù)庫(kù)中增加一條記錄。該記錄的ID 值為1,可以采用下面的SQL 語(yǔ)句。 INSERT INTO userinfo(userId, name, age) VALUES( '1', 'galaxy', 32 ); 接下來(lái)在運(yùn)行測(cè)試主程序時(shí),應(yīng)該看到類似下面的輸出: Hibernate: select user0_.userId as userId0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=? ID: 1 Namge: galaxy Age: 32 ID: 1 Namge: galaxy Age: 32 通過(guò)上面的結(jié)果可以看到,每個(gè)運(yùn)行的進(jìn)程都輸出了User 對(duì)象的信息,但在運(yùn)行中只進(jìn)行了一次數(shù)據(jù)庫(kù)讀取操作,這說(shuō)明第二次User 對(duì)象的獲得是從緩存中抓取的,而沒(méi)有進(jìn)行數(shù)據(jù)庫(kù)的查詢操作。 14.2.3.7 查詢緩存 查詢緩存是專門針對(duì)各種查詢操作進(jìn)行緩存。查詢緩存會(huì)在整個(gè)SessionFactory 的生命周期中起作用,存儲(chǔ)的方式也是采用key-value 的形式來(lái)進(jìn)行存儲(chǔ)的。 查詢緩存中的key 是根據(jù)查詢的語(yǔ)句、查詢的條件、查詢的參數(shù)和查詢的頁(yè)數(shù)等信息組成的。而數(shù)據(jù)的存儲(chǔ)則會(huì)使用兩種方式,使用SELECT 語(yǔ)句只查詢實(shí)體對(duì)象的某些列或者某些實(shí)體對(duì)象列的組合時(shí),會(huì)直接緩存整個(gè)結(jié)果集。而對(duì)于查詢結(jié)果為某個(gè)實(shí)體對(duì)象集合的情況則只會(huì)緩存實(shí)體對(duì)象的ID 值,以達(dá)到緩存空間可以共用,節(jié)省空間的目的。 在使用查詢緩存時(shí),除了需要設(shè)置hibernate.cache.provider_class參數(shù)來(lái)啟動(dòng) 二級(jí)緩存外,還需要通過(guò)hibernate.cache.use_query_cache參數(shù)來(lái)啟動(dòng)對(duì)查詢緩存的支持。 另外需要注意的是,查詢緩存是在執(zhí)行查詢語(yǔ)句的時(shí)候指定緩存的方式以及是否需要對(duì)查詢的結(jié)果進(jìn)行緩存。 下面就來(lái)了解一下查詢緩存的使用方法及作用。 修改Hibernate 配置文件 首先需要修改Hibernate 的配置文件,增加hibernate.cache.use_query_cache參數(shù)的配置。配置方法如下: Hibernate 配置文件的詳細(xì)內(nèi)容請(qǐng)參考配套光盤中的hibernatesrccnhxex hibernate?chehibernate.cfg.xml文件。 編寫(xiě)主測(cè)試程序 由于這是在前面二級(jí)緩存例子的基礎(chǔ)上來(lái)開(kāi)發(fā)的,所以,對(duì)于EHCache 的配置以及視圖對(duì)象的開(kāi)發(fā)和映射文件的配置工作就都不需要再重新進(jìn)行了。下面就來(lái)看一下主測(cè)試程序的實(shí)現(xiàn)方法,如清單14.11所示。 清單14.11 主程序的實(shí)現(xiàn) …… public void run() { SessionFactory sf = QueryCacheMain.getSessionFactory(); Session session = sf.getCurrentSession(); session.beginTransaction(); Query query = session.createQuery( "from User" ); Iterator it = query.setCacheable( true ).list().iterator(); while( it.hasNext() ) { System.out.println( it.next() ); } User user = (User)session.get( User.class, "1" ); System.out.println( user ); session.getTransaction().commit(); } public static void main(String[] args) { QueryCacheMain main1 = new QueryCacheMain(); main1.start(); try { Thread.sleep( 2000 ); } catch (InterruptedException e) { e.printStackTrace(); } QueryCacheMain main2 = new QueryCacheMain(); main2.start(); } } 主程序在實(shí)現(xiàn)的時(shí)候采用了多線程的方式來(lái)運(yùn)行。首先將“from User”查詢結(jié)果進(jìn)行緩存,然后再通過(guò)ID 取得對(duì)象來(lái)檢查是否對(duì)對(duì)象進(jìn)行了緩存。另外,多個(gè)線程的執(zhí)行可以看出對(duì)于進(jìn)行了緩存的查詢是不會(huì)執(zhí)行第二次的。 運(yùn)行測(cè)試主程序 接著就來(lái)運(yùn)行測(cè)試主程序,其輸出結(jié)果應(yīng)該如下所示: Hibernate: select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_ from USERINFO user0_ ID: 1 Namge: galaxy Age: 32 ID: 1 Namge: galaxy Age: 32 ID: 1 Namge: galaxy Age: 32 ID: 1 Namge: galaxy Age: 32 通過(guò)上面的執(zhí)行結(jié)果可以看到,在兩個(gè)線程執(zhí)行中,只執(zhí)行了一個(gè)SQL 查詢語(yǔ)句。這是因?yàn)楦鶕?jù)ID 所要獲取的對(duì)象在前面的查詢中已經(jīng)得到了,并進(jìn)行了緩存,所以沒(méi)有再次執(zhí)行查詢語(yǔ)句。