與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.1 Hibernate所支持的二級(jí)緩存組件
組件
|
Provider類
|
類型
|
集群
|
查詢緩存
|
Hashtable
|
org.hibernate.cache.HashtableCacheProvider
|
內(nèi)存
|
不支持
|
支持
|
EHCache
|
org.hibernate.cache.EhCacheProvider
|
內(nèi)存,硬盤
|
最新支持
|
支持
|
OSCache
|
org.hibernate.cache.OSCacheProvider
|
內(nèi)存,硬盤
|
不支持
|
支持
|
SwarmCache
|
org.hibernate.cache.SwarmCacheProvider
|
集群
|
支持
|
不支持
|
JBoss TreeCache
|
org.hibernate.cache.TreeCacheProvider
|
集群
|
支持
|
支持
|
Hibernate已經(jīng)不再提供對(duì)JCS(Java Caching System)組件的支持了。
14.2.3.4 二級(jí)緩存的配置
在使用Hibernate的二級(jí)緩存時(shí),對(duì)于每個(gè)需要使用二級(jí)緩存的對(duì)象都需要進(jìn)行相應(yīng)的配置工作。也就是說(shuō),只有配置了使用二級(jí)緩存的對(duì)象才會(huì)被放置在二級(jí)緩存中。二級(jí)緩存是通過(guò)<cache>元素來(lái)進(jìn)行配置的。<cache>元素的屬性定義說(shuō)明如下所示:
<cache

usage="transactional|read-write|nonstrict-read-write|read-only" (1)

region="RegionName" (2)

include="all|non-lazy" (3)

/>

<cache>

元素的屬性說(shuō)明如表14.2所示。
表14.2 <cache>元素的屬性說(shuō)明
序號(hào)
|
屬性
|
含義和作用
|
必須
|
默認(rèn)值
|
(1)
|
usage
|
指定緩存策略,可選的策略包括:transactional,read-write,nonstrict-read-write或read-only
|
Y
|
(2)
|
region
|
指定二級(jí)緩存區(qū)域名
|
N
|
(3)
|
include
|
指定是否緩存延遲加載的對(duì)象。all,表示緩存所有對(duì)象;non-lazy,表示不緩存延遲加載的對(duì)象
|
N
|
all
|
14.2.3.5 二級(jí)緩存的策略
當(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)策略。
● 讀/寫緩存(read-write)
如果應(yīng)用程序需要更新數(shù)據(jù),可能read-write緩存比較合適。如果需要序列化事務(wù)隔離級(jí)別,那么就不能使用這種緩存策略。
對(duì)于經(jīng)常被讀但很少修改的數(shù)據(jù),可以采用這種隔離類型,因?yàn)樗梢苑乐古K讀這類的并發(fā)問(wèn)題。
● 不嚴(yán)格的讀/寫緩存(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í)別最高,然后依次是讀/寫型和不嚴(yán)格讀寫型,只讀型的隔離級(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。具體要增加的配置如下所示:
<property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider</property>

Hibernate配置文件的詳細(xì)內(nèi)容請(qǐng)參考配套光盤中的hibernate"src"cn"hxex" hibernate"cache"hibernate.cfg.xml文件。
增加EHCache配置參數(shù)
在默認(rèn)情況下,EHCache會(huì)到classpath所指定的路徑中尋找ehcache.xml文件來(lái)作為EHCache的配置文件。
在配置文件中,包含了EHCache進(jìn)行緩存管理時(shí)的一些基本的參數(shù)。具體的配置方法如清單14.9所示。
清單14.9 EHCache的配置
<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="ehcache.xsd">

<diskStore path="java.io.tmpdir" />

<defaultCache

maxElementsInMemory="10000"

eternal="false"

timeToIdleSeconds="120"

timeToLiveSeconds="120"

overflowToDisk="true"

diskPersistent="false"

diskExpiryThreadIntervalSeconds="120"

memoryStoreEvictionPolicy="LRU" />

</ehcache>


在這里只是使用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)配套光盤中的hibernate"src"cn"hxex"cache"User.java文件。
配置映射文件
映射文件的配置與不使用二級(jí)緩存的Java對(duì)象的區(qū)別就在于需要增加前面所介紹的<cache>元素來(lái)配置此對(duì)象的緩存策略。在這里所使用的緩存策略為“read-write”。所以,應(yīng)該在映射文件中增加如下的配置:
<cache usage="read-write"/>
映射文件的詳細(xì)配置請(qǐng)參考配套光盤中的hibernate"src"cn"hxex"cache"User.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ù)的配置。配置方法如下:
<property name="hibernate.cache.use_query_cache">true</property>
Hibernate配置文件的詳細(xì)內(nèi)容請(qǐng)參考配套光盤中的hibernate"src"cn"hxex" hibernate"cache"hibernate.cfg.xml文件。
編寫主測(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ǔ)句。