以下內容由 我的漫漫程序之旅 博客轉載 地址:http://www.blogjava.net/supercrsky/articles/238580.html
---------------------------------------------------分割線-------------------------------------------------
-----------------Hibernate二級緩存詳解-----------------------
與Session相對的是,SessionFactory也提供了相應的緩存機制。SessionFactory緩存可以依據功能和目的的不同而劃分爲內置緩存和外置緩存。
SessionFactory的內置緩存中存放了映射元數據和預定義SQL語句,映射元數據是映射文件中數據的副本,而預定義SQL語句是在 Hibernate初始化階段根據映射元數據推導出來的。
SessionFactory的內置緩存是隻讀的,應用程序不能修改緩存中的映射元數據和預定義 SQL語句,因此SessionFactory不需要進行內置緩存與映射文件的同步。
SessionFactory的外置緩存是一個可配置的插件。在默認情況下,SessionFactory不會啓用這個插件。外置緩存的數據是數據庫數據 的副本,外置緩存的介質可以是內存或者硬盤。
SessionFactory的外置緩存也被稱爲Hibernate的二級緩存。
Hibernate的二級緩存的實現原理與一級緩存是一樣的,也是通過以ID爲key的Map來實現對對象的緩存。
由於Hibernate的二級緩存是作用在SessionFactory範圍內的,因而它比一級緩存的範圍更廣,可以被所有的Session對象所共享。
1、二級緩存的工作內容
Hibernate的二級緩存同一級緩存一樣,也是針對對象ID來進行緩存。所以說,二級緩存的作用範圍是針對根據ID獲得對象的查詢。
二級緩存的工作可以概括爲以下幾個部分:
● 在執行各種條件查詢時,如果所獲得的結果集爲實體對象的集合,那麼就會把所有的數據對象根據ID放入到二級緩存中。
● 當Hibernate根據ID訪問數據對象的時候,首先會從Session一級緩存中查找,如果查不到並且配置了二級緩存,那麼會從二級緩存中查找,如果還查不到,就會查詢數據庫,把結果按照ID放入到緩存中。
● 刪除、更新、增加數據的時候,同時更新緩存。
2、二級緩存的適用範圍
Hibernate的二級緩存作爲一個可插入的組件在使用的時候也是可以進行配置的,但並不是所有的對象都適合放在二級緩存中。
在通常情況下會將具有以下特徵的數據放入到二級緩存中:
● 很少被修改的數據。
● 不是很重要的數據,允許出現偶爾併發的數據。
● 不會被併發訪問的數據。
● 參考數據。
而對於具有以下特徵的數據則不適合放在二級緩存中:
● 經常被修改的數據。
● 財務數據,絕對不允許出現併發。
● 與其他應用共享的數據。
在這裏特別要注意的是對放入緩存中的數據不能有第三方的應用對數據進行更改(其中也包括在自己程序中使用其他方式進行數據的修改,例如,JDBC),
因爲那樣Hibernate將不會知道數據已經被修改,也就無法保證緩存中的數據與數據庫中數據的一致性。
3、二級緩存組件
在默認情況下,Hibernate會使用EHCache作爲二級緩存組件。但是,可以通過設置 hibernate.cache.provider_class屬性,指定其他的緩存策略,該緩存策略必須實現 org.hibernate.cache.CacheProvider接口。
通過實現org.hibernate.cache.CacheProvider接口可以提供對不同二級緩存組件的支持。
Hibernate內置支持的二級緩存組件如表1所示。
表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)組件的支持了。
4、二級緩存的配置
在使用Hibernate的二級緩存時,對於每個需要使用二級緩存的對象都需要進行相應的配置工作。也就是說,只有配置了使用二級緩存的對象纔會被放置在二級緩存中。
二級緩存是通過<cache>元素來進行配置的。<cache>元素的屬性定義說明如下所示:
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
region="RegionName" (2)
include="all|non-lazy" (3)
/>
<cache>
元素的屬性說明如表2所示。
表2 <cache>元素的屬性說明
序號 |
屬性 |
含義和作用 |
必須 |
默認值 |
(1) |
usage |
指定緩存策略,可選的策略包括:transactional,read-write,nonstrict-read-write或read-only |
Y |
|
(2) |
region |
指定二級緩存區域名 |
N |
|
(3) |
include |
指定是否緩存延遲加載的對象。all,表示緩存所有對象;non-lazy,表示不緩存延遲加載的對象 |
N |
all |
5、二級緩存的策略
當多個併發的事務同時訪問持久化層的緩存中的相同數據時,會引起併發問題,必須採用必要的事務隔離措施。
在進程範圍或集羣範圍的緩存,即第二級緩存,會出現併發問題。因此可以設定以下4種類型的併發訪問策略,每一種策略對應一種事務隔離級別。
● 只讀緩存(read-only)
如果應用程序需要讀取一個持久化類的實例,但是並不打算修改它們,可以使用read-only緩存。這是最簡單,也是實用性最好的策略。
對於從來不會修改的數據,如參考數據,可以使用這種併發訪問策略。
● 讀/寫緩存(read-write)
如果應用程序需要更新數據,可能read-write緩存比較合適。如果需要序列化事務隔離級別,那麼就不能使用這種緩存策略。
對於經常被讀但很少修改的數據,可以採用這種隔離類型,因爲它可以防止髒讀這類的併發問題。
● 不嚴格的讀/寫緩存(nonstrict-read-write)
如果程序偶爾需要更新數據(也就是說,出現兩個事務同時更新同一個條目的現象很不常見),也不需要十分嚴格的事務隔離,可能適用nonstrict-read-write緩存。
對於極少被修改,並且允許偶爾髒讀的數據,可以採用這種併發訪問策略。
● 事務緩存(transactional)
transactional緩存策略提供了對全事務的緩存,僅僅在受管理環境中使用。它提供了Repeatable Read事務隔離級別。對於經常被讀但很少修改的數據,可以採用這種隔離類型,因爲它可以防止髒讀和不可重複讀這類的併發問題。
在上面所介紹的隔離級別中,事務型併發訪問策略的隔離級別最高,然後依次是讀/寫型和不嚴格讀寫型,只讀型的隔離級別最低。事務的隔離級別越高,併發性能就越低。
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>
增加EHCache配置參數
在默認情況下,EHCache會到classpath所指定的路徑中尋找ehcache.xml文件來作爲EHCache的配置文件。
在配置文件中,包含了EHCache進行緩存管理時的一些基本的參數。具體的配置方法如清單1所示。
清單1 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所示。
清單2 測試主程序的實現
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對象的獲得是從緩存中抓取的,而沒有進行數據庫的查詢操作。
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所示。
清單3 主程序的實現
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所要獲取的對象在前面的查詢中已經得到了,並進行了緩存,所以沒有再次執行查詢語句。
---------------------------------------------------完---------------------------------------------------