與Session相對的是,SessionFactory也提供了相應的緩存機制。SessionFactory緩存可以依據功能和目的的不同而劃分為內置緩存和外置緩存。
SessionFactory的內置緩存中存放了映射元數據和預定義SQL語句,映射元數據是映射文件中數據的副本,而預定義SQL語句是在 Hibernate初始化階段根據映射元數據推導出來的。SessionFactory的內置緩存是只讀的,應用程序不能修改緩存中的映射元數據和預定義 SQL語句,因此SessionFactory不需要進行內置緩存與映射文件的同步。
SessionFactory的外置緩存是一個可配置的插件。在默認情況下,SessionFactory不會啟用這個插件。外置緩存的數據是數據庫數據 的副本,外置緩存的介質可以是內存或者硬盤。SessionFactory的外置緩存也被稱為Hibernate的二級緩存。
Hibernate的二級緩存的實現原理與一級緩存是一樣的,也是通過以ID為key的Map來實現對對象的緩存。
由于Hibernate的二級緩存是作用在SessionFactory范圍內的,因而它比一級緩存的范圍更廣,可以被所有的Session對象所共享。
14.2.3.1 二級緩存的工作內容
Hibernate的二級緩存同一級緩存一樣,也是針對對象ID來進行緩存。所以說,二級緩存的作用范圍是針對根據ID獲得對象的查詢。
二級緩存的工作可以概括為以下幾個部分:
● 在執行各種條件查詢時,如果所獲得的結果集為實體對象的集合,那么就會把所有的數據對象根據ID放入到二級緩存中。
● 當Hibernate根據ID訪問數據對象的時候,首先會從Session一級緩存中查找,如果查不到并且配置了二級緩存,那么會從二級緩存中查找,如果還查不到,就會查詢數據庫,把結果按照ID放入到緩存中。
● 刪除、更新、增加數據的時候,同時更新緩存。
14.2.3.2 二級緩存的適用范圍
Hibernate的二級緩存作為一個可插入的組件在使用的時候也是可以進行配置的,但并不是所有的對象都適合放在二級緩存中。
在通常情況下會將具有以下特征的數據放入到二級緩存中:
● 很少被修改的數據。
● 不是很重要的數據,允許出現偶爾并發的數據。
● 不會被并發訪問的數據。
● 參考數據。
而對于具有以下特征的數據則不適合放在二級緩存中:
● 經常被修改的數據。
● 財務數據,絕對不允許出現并發。
● 與其他應用共享的數據。
在這里特別要注意的是對放入緩存中的數據不能有第三方的應用對數據進行更改(其中也包括在自己程序中使用其他方式進行數據的修改,例如,JDBC),因為那樣Hibernate將不會知道數據已經被修改,也就無法保證緩存中的數據與數據庫中數據的一致性。
14.2.3.3 二級緩存組件
在默認情況下,Hibernate會使用EHCache作為二級緩存組件。但是,可以通過設置 hibernate.cache.provider_class屬性,指定其他的緩存策略,該緩存策略必須實現 org.hibernate.cache.CacheProvider接口。
通過實現org.hibernate.cache.CacheProvider接口可以提供對不同二級緩存組件的支持。
Hibernate內置支持的二級緩存組件如表14.1所示。
表14.1 Hibernate所支持的二級緩存組件
組件
|
Provider類
|
類型
|
集群
|
查詢緩存
|
Hashtable
|
org.hibernate.cache.HashtableCacheProvider
|
內存
|
不支持
|
支持
|
EHCache
|
org.hibernate.cache.EhCacheProvider
|
內存,硬盤
|
最新支持
|
支持
|
OSCache
|
org.hibernate.cache.OSCacheProvider
|
內存,硬盤
|
不支持
|
支持
|
SwarmCache
|
org.hibernate.cache.SwarmCacheProvider
|
集群
|
支持
|
不支持
|
JBoss TreeCache
|
org.hibernate.cache.TreeCacheProvider
|
集群
|
支持
|
支持
|
Hibernate已經不再提供對JCS(Java Caching System)組件的支持了。
14.2.3.4 二級緩存的配置
在使用Hibernate的二級緩存時,對于每個需要使用二級緩存的對象都需要進行相應的配置工作。也就是說,只有配置了使用二級緩存的對象才會被放置在二級緩存中。二級緩存是通過<cache>元素來進行配置的。<cache>元素的屬性定義說明如下所示:
<cache

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

region="RegionName" (2)

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

/>

<cache>

元素的屬性說明如表14.2所示。
表14.2 <cache>元素的屬性說明
序號
|
屬性
|
含義和作用
|
必須
|
默認值
|
(1)
|
usage
|
指定緩存策略,可選的策略包括:transactional,read-write,nonstrict-read-write或read-only
|
Y
|
(2)
|
region
|
指定二級緩存區域名
|
N
|
(3)
|
include
|
指定是否緩存延遲加載的對象。all,表示緩存所有對象;non-lazy,表示不緩存延遲加載的對象
|
N
|
all
|
14.2.3.5 二級緩存的策略
當多個并發的事務同時訪問持久化層的緩存中的相同數據時,會引起并發問題,必須采用必要的事務隔離措施。
在進程范圍或集群范圍的緩存,即第二級緩存,會出現并發問題。因此可以設定以下4種類型的并發訪問策略,每一種策略對應一種事務隔離級別。
● 只讀緩存(read-only)
如果應用程序需要讀取一個持久化類的實例,但是并不打算修改它們,可以使用read-only緩存。這是最簡單,也是實用性最好的策略。
對于從來不會修改的數據,如參考數據,可以使用這種并發訪問策略。
● 讀/寫緩存(read-write)
如果應用程序需要更新數據,可能read-write緩存比較合適。如果需要序列化事務隔離級別,那么就不能使用這種緩存策略。
對于經常被讀但很少修改的數據,可以采用這種隔離類型,因為它可以防止臟讀這類的并發問題。
● 不嚴格的讀/寫緩存(nonstrict-read-write)
如果程序偶爾需要更新數據(也就是說,出現兩個事務同時更新同一個條目的現象很不常見),也不需要十分嚴格的事務隔離,可能適用nonstrict-read-write緩存。
對于極少被修改,并且允許偶爾臟讀的數據,可以采用這種并發訪問策略。
● 事務緩存(transactional)
transactional緩存策略提供了對全事務的緩存,僅僅在受管理環境中使用。它提供了Repeatable Read事務隔離級別。對于經常被讀但很少修改的數據,可以采用這種隔離類型,因為它可以防止臟讀和不可重復讀這類的并發問題。
在上面所介紹的隔離級別中,事務型并發訪問策略的隔離級別最高,然后依次是讀/寫型和不嚴格讀寫型,只讀型的隔離級別最低。事務的隔離級別越高,并發性能就越低。
14.2.3.6 在開發中使用二級緩存
在這一部分中,將細致地介紹如何在Hibernate中使用二級緩存。在這里所使用的二級緩存組件為EHCache。
關于EHCache的詳細信息請參考http://ehcache.sourceforge.net上的內容。
在Hibernate中使用二級緩存需要經歷以下步驟:
● 在Hibernate配置文件(通常為hibernate.cfg.xml)中,設置二級緩存的提供者類。
● 配置EHCache的基本參數。
● 在需要進行緩存的實體對象的映射文件中配置緩存的策略。
下面就來逐步演示一下如何在開發中使用Hibernate的二級緩存。
修改Hibernate的配置文件
在使用Hibernate的二級緩存時,需要在Hibernate的配置文件中指定緩存提供者對象,以便于Hibernate可以通過其實現對數據的緩存處理。
在這里需要設置的參數是hibernate.cache.provider_class,在使用EHCache時,需要將其值設置為org.hibernate.cache.EhCacheProvider。具體要增加的配置如下所示:
<property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider</property>

Hibernate配置文件的詳細內容請參考配套光盤中的hibernate"src"cn"hxex" hibernate"cache"hibernate.cfg.xml文件。
增加EHCache配置參數
在默認情況下,EHCache會到classpath所指定的路徑中尋找ehcache.xml文件來作為EHCache的配置文件。
在配置文件中,包含了EHCache進行緩存管理時的一些基本的參數。具體的配置方法如清單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所提供的默認配置文件進行了EHCache的基本配置,對于這些參數的詳細含義請參考其官方網站(http: //ehcache.sourceforge.net/)中的資料。在實際的開發中,應該依據自己的具體情況來設置這些參數的值。
開發實體對象
這里所使用的是一個非常簡單的User對象,它只包含了ID,name和age三個屬性,具體的實現方法請參見配套光盤中的hibernate"src"cn"hxex"cache"User.java文件。
配置映射文件
映射文件的配置與不使用二級緩存的Java對象的區別就在于需要增加前面所介紹的<cache>元素來配置此對象的緩存策略。在這里所使用的緩存策略為“read-write”。所以,應該在映射文件中增加如下的配置:
<cache usage="read-write"/>
映射文件的詳細配置請參考配套光盤中的hibernate"src"cn"hxex"cache"User.hbm.xml文件。
測試主程序
在這里的測試主程序采用了多線程的運行方式,以模擬在不同Session的情況下是否真的可以避免查詢的重復進行。
在這個測試程序中,所做的工作就是依據ID來得到對應的實體對象,并將其輸出。然后通過多次運行此程序,來檢查后面的運行是否進行了數據庫的操作。
測試主程序的主要測試方法的實現如清單14.10所示。
清單14.10 測試主程序的實現
……

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();
}

}


運行測試程序
在運行測試程序之前,需要先手動地向數據庫中增加一條記錄。該記錄的ID值為1,可以采用下面的SQL語句。
INSERT INTO userinfo(userId, name, age) VALUES( '1', 'galaxy', 32 );
接下來在運行測試主程序時,應該看到類似下面的輸出:
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
通過上面的結果可以看到,每個運行的進程都輸出了User對象的信息,但在運行中只進行了一次數據庫讀取操作,這說明第二次User對象的獲得是從緩存中抓取的,而沒有進行數據庫的查詢操作。
14.2.3.7 查詢緩存
查詢緩存是專門針對各種查詢操作進行緩存。查詢緩存會在整個SessionFactory的生命周期中起作用,存儲的方式也是采用key-value的形式來進行存儲的。
查詢緩存中的key是根據查詢的語句、查詢的條件、查詢的參數和查詢的頁數等信息組成的。而數據的存儲則會使用兩種方式,使用SELECT語句只查詢實體 對象的某些列或者某些實體對象列的組合時,會直接緩存整個結果集。而對于查詢結果為某個實體對象集合的情況則只會緩存實體對象的ID值,以達到緩存空間可 以共用,節省空間的目的。
在使用查詢緩存時,除了需要設置hibernate.cache.provider_class參數來啟動二級緩存外,還需要通過hibernate.cache.use_query_cache參數來啟動對查詢緩存的支持。
另外需要注意的是,查詢緩存是在執行查詢語句的時候指定緩存的方式以及是否需要對查詢的結果進行緩存。
下面就來了解一下查詢緩存的使用方法及作用。
修改Hibernate配置文件
首先需要修改Hibernate的配置文件,增加hibernate.cache.use_query_cache參數的配置。配置方法如下:
<property name="hibernate.cache.use_query_cache">true</property>
Hibernate配置文件的詳細內容請參考配套光盤中的hibernate"src"cn"hxex" hibernate"cache"hibernate.cfg.xml文件。
編寫主測試程序
由于這是在前面二級緩存例子的基礎上來開發的,所以,對于EHCache的配置以及視圖對象的開發和映射文件的配置工作就都不需要再重新進行了。下面就來看一下主測試程序的實現方法,如清單14.11所示。
清單14.11 主程序的實現
……

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();

}

}


主程序在實現的時候采用了多線程的方式來運行。首先將“from User”查詢結果進行緩存,然后再通過ID取得對象來檢查是否對對象進行了緩存。另外,多個線程的執行可以看出對于進行了緩存的查詢是不會執行第二次的。
運行測試主程序
接著就來運行測試主程序,其輸出結果應該如下所示:
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
通過上面的執行結果可以看到,在兩個線程執行中,只執行了一個SQL查詢語句。這是因為根據ID所要獲取的對象在前面的查詢中已經得到了,并進行了緩存,所以沒有再次執行查詢語句。