Posted on 2008-03-22 09:50
E.ven 閱讀(1594)
評(píng)論(0) 編輯 收藏
hibernate緩存
(一)hibernate數(shù)據(jù)緩存策略
緩存是數(shù)據(jù)庫(kù)數(shù)據(jù)在內(nèi)存中的臨時(shí)容器,它包含了庫(kù)表數(shù)據(jù)在內(nèi)存中的拷貝,位于數(shù)據(jù)庫(kù)與數(shù)據(jù)訪問層之間。對(duì)于查詢操作相當(dāng)頻繁的系統(tǒng)(論壇,新聞發(fā)布等),良好的緩存機(jī)制顯得尤為重要。
ORM在進(jìn)行數(shù)據(jù)讀取時(shí),首先在緩存中查詢,避免了數(shù)據(jù)庫(kù)調(diào)用的性能開銷。
ORM的數(shù)據(jù)緩存應(yīng)包含下面幾個(gè)層次:
1)事務(wù)級(jí)緩存 2)應(yīng)用級(jí)緩存 3)分布式緩存
具體針對(duì)Hibernate而言,采用兩級(jí)緩存策略,其過程描述:
(1)條件查詢的時(shí)候,總是發(fā)出一條select * from table_name where …. 這樣的SQL語(yǔ)句查詢數(shù)據(jù)庫(kù),一次獲得所有的數(shù)據(jù)對(duì)象。
(2) 把獲得的所有數(shù)據(jù)對(duì)象根據(jù)ID放入到第二級(jí)緩存中。
(3) 當(dāng)Hibernate根據(jù)ID訪問數(shù)據(jù)對(duì)象的時(shí)候,首先從Session一級(jí)緩存中查;查不到,如果配置了二級(jí)緩存,那么從二級(jí)緩存中查;查不到,再查詢數(shù)據(jù)庫(kù),把結(jié)果按照ID放入到緩存。
(4) 刪除、更新、增加數(shù)據(jù)的時(shí)候,同時(shí)更新緩存。
1. 一級(jí)緩存(session level)-數(shù)據(jù)庫(kù)事務(wù)級(jí)緩存
1)根據(jù)主鍵id加載數(shù)據(jù)時(shí)。 Session.load(), Session.iterate()方法
2)延遲加載時(shí)
Session內(nèi)部維護(hù)一個(gè)數(shù)據(jù)對(duì)象集合,包括了本Session內(nèi)選取的、操作的數(shù)據(jù)對(duì)象。這稱為Session內(nèi)部緩存,是Hibernate的第一級(jí)最快緩存,屬于Hibernate的既定行為,不需要進(jìn)行配置(也沒有辦法配置 :-)。
內(nèi)部緩存正常情況下由hibernate自動(dòng)維護(hù),但也可人工干預(yù):
1) Session.evict (): 將某個(gè)特定對(duì)象從內(nèi)部緩存中清除
2)Session.clear(): 清空內(nèi)部緩存
2.二級(jí)緩存(SessionFactory level)-應(yīng)用級(jí)緩存
二級(jí)緩存由SessionFactory的所有session實(shí)例共享。
3. 第三方緩存實(shí)現(xiàn)
EHCache, OSCahe
hibernate批量查詢引起的內(nèi)存溢出問題
批量查詢基本不適合使用現(xiàn)有的持久層技術(shù)來做,如CMP或hibernate,IBatis倒是可以.
因?yàn)槊看握{(diào)用Session.save()方法時(shí),當(dāng)前session都會(huì)將對(duì)象納入到自身的內(nèi)部緩存中。內(nèi)部緩存不同于二級(jí)緩存,我們可以在二級(jí)緩存的配置中指定其最大容量。
解決方案:
1)在批處理情況下,關(guān)閉Hibernate緩存,如果關(guān)閉Hibernate緩存,那么和直接使用JDBC就沒有區(qū)別。
2) 每隔一段時(shí)間清空Session內(nèi)部緩存
Session實(shí)現(xiàn)了異步write-behind,它允許Hibernate顯式地寫操作的批處理。 這里,我給出Hibernate如何實(shí)現(xiàn)批量插入的方法: 首先,我們?cè)O(shè)置一個(gè)合理的JDBC批處理大小,hibernate.jdbc.batch_size 20。 然后在一定間隔對(duì)Session進(jìn)行flush()和clear()。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 ) {
//flush 插入數(shù)據(jù)和釋放內(nèi)存:
session.flush(); session.clear(); }
}
tx.commit();
session.close();
為了優(yōu)化性能,可執(zhí)行批量操作。在傳統(tǒng)的JDBC編程中,批量操作方式如下,將數(shù)個(gè)SQL操作批量提交:
PrepareStatement ps=conn.prepareStatement("insert into users(name) values(?)");
for(int i=0;i<100000;i++){
ps.setString(1, "user"+i);
ps.addBatch();
}
int[] counts=ps.executeBatch
在Hibernate中,可以設(shè)置hibernate.jdbc.batch_size 參數(shù)來指定每次提交的sql數(shù)量。
hibernate2和hibernate3數(shù)據(jù)批量刪除機(jī)制分析
1.hibernate2
Transaction tx=session.beginTransaction();
session.delete("from users");
tx.commit();
觀察日志輸出:
select ... from users
Hibernate:delete from users where id=?
Hibernate:delete from users where id=?
Hibernate:delete from users where id=?
...
hibernate2版本會(huì)首先從數(shù)據(jù)庫(kù)中查詢出所有符合條件的記錄,再對(duì)此記錄循環(huán)刪除。如果記錄量過大,勢(shì)必引起內(nèi)存溢出和刪除效率問題。ORM為什么要這么做呢?因?yàn)镺RM為了自動(dòng)維護(hù)內(nèi)存狀態(tài),必須知道用戶到底對(duì)哪些數(shù)據(jù)進(jìn)行了操作。問題的解決方法:
1)內(nèi)存消耗
批量刪除前首先從數(shù)據(jù)庫(kù)中查詢出所有符合條件的記錄,如果數(shù)據(jù)量過大,就會(huì)導(dǎo)致 OutOfMemoryError.
可以采用Session .iterate或Query.iterate方法逐條獲取記錄,再執(zhí)行delete操作。另外,hibernate2.16后的版本提供了基于游標(biāo)的數(shù)據(jù)遍歷操作:
Transaction tx=session.beginTransaction();
String hql="from users";
Query query=session.createQrery(hql);
ScrollableResults sr=query.scroll();
while(sr.next()){
TUser user=(TUser)sr.get(0);
session.delete();
}
tx.commit();
2)循環(huán)刪除的效率問題
由于hibernate在批量刪除操作過程中,需要反復(fù)調(diào)用delete SQL,存在性能問題。我們?nèi)匀豢梢酝ㄟ^調(diào)整hibernate.jdbc.batch_size參數(shù)來解決。
2.hibernate3
hibernate3 HQL中引入了 bulk delete/update操作, 即通過一條獨(dú)立的sql語(yǔ)句來完成數(shù)據(jù)的批量操作。
Transaction tx=session.beginTransaction();
String hql="delete TUser";
Query query=session.createQrery(hql);
int count=query.executeUpdate();
tx.commit();
觀察日志輸出:
Hibernate:delete from TUser
(二)ibatis數(shù)據(jù)緩存
相對(duì)Hibernate 等封裝較為嚴(yán)密的ORM 實(shí)現(xiàn)而言(因?yàn)閷?duì)數(shù)據(jù)對(duì)象的操作實(shí)現(xiàn)
了較為嚴(yán)密的封裝,可以保證其作用范圍內(nèi)的緩存同步,而ibatis 提供的是半封閉
的封裝實(shí)現(xiàn),因此對(duì)緩存的操作難以做到完全的自動(dòng)化同步)。
ibatis 的緩存機(jī)制使用必須特別謹(jǐn)慎。特別是flushOnExecute 的設(shè)定(見
“ibatis配置”一節(jié)中的相關(guān)內(nèi)容),需要考慮到所有可能引起實(shí)際數(shù)據(jù)與緩存數(shù)據(jù)
不符的操作。如本模塊中其他Statement對(duì)數(shù)據(jù)的更新,其他模塊對(duì)數(shù)據(jù)的更新,甚
至第三方系統(tǒng)對(duì)數(shù)據(jù)的更新。否則,臟數(shù)據(jù)的出現(xiàn)將為系統(tǒng)的正常運(yùn)行造成極大隱患。
如果不能完全確定數(shù)據(jù)更新操作的波及范圍,建議避免Cache的盲目使用。
1.iBatis cache設(shè)置
sqlmap-config.xml在<sqlMapConfig>里面加入
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true" />
maps.xml在<sqlMap>里面加入
<cacheModel id="userCache" type="LRU" readonly="true" serialize="false">
<flushInterval hours="24"/>
<flushOnExecute statement="insertTest"/>
<property name="size" value="1000" />
</cacheModel>
可以看到,Cache有如下幾個(gè)比較重要的屬性:readOnly,serialize,type
readOnly
readOnly值的是緩存中的數(shù)據(jù)對(duì)象是否只讀。這里的只讀并不是意味著數(shù)據(jù)對(duì)象一
旦放入緩存中就無法再對(duì)數(shù)據(jù)進(jìn)行修改。而是當(dāng)數(shù)據(jù)對(duì)象發(fā)生變化的時(shí)候,如數(shù)據(jù)對(duì)
象的某個(gè)屬性發(fā)生了變化,則此數(shù)據(jù)對(duì)象就將被從緩存中廢除,下次需要重新從數(shù)據(jù)
庫(kù)讀取數(shù)據(jù),構(gòu)造新的數(shù)據(jù)對(duì)象。
serialize
如果需要全局的數(shù)據(jù)緩存,CacheModel的serialize屬性必須被設(shè)為true。否則數(shù)據(jù)緩存只對(duì)當(dāng)前Session(可簡(jiǎn)單理解為當(dāng)前線程)有效,局部緩存對(duì)系統(tǒng)的整體性能提升有限。
Cache Type:
與hibernate類似,ibatis通過緩沖接口的插件式實(shí)現(xiàn),提供了多種Cache的實(shí)現(xiàn)機(jī)制可供選擇:
1. MEMORY
2. LRU
3. FIFO
4. OSCACHE
MEMORY類型Cache與WeakReference
MEMORY 類型的Cache 實(shí)現(xiàn),實(shí)際上是通過Java 對(duì)象引用進(jìn)行。ibatis 中,其實(shí)現(xiàn)類
為com.ibatis.db.sqlmap.cache.memory.MemoryCacheController,MemoryCacheController 內(nèi)部,
使用一個(gè)HashMap來保存當(dāng)前需要緩存的數(shù)據(jù)對(duì)象的引用。
LRU型Cache
當(dāng)Cache達(dá)到預(yù)先設(shè)定的最大容量時(shí),ibatis會(huì)按照“最少使用”原則將使用頻率最少
的對(duì)象從緩沖中清除。可配置的參數(shù)有:
flushInterval:指定了多長(zhǎng)時(shí)間清除緩存,上例中指定每24小時(shí)強(qiáng)行清空緩存區(qū)的所有內(nèi)容。
size
FIFO型Cache
先進(jìn)先出型緩存,最先放入Cache中的數(shù)據(jù)將被最先廢除。
OSCache
(三)開源數(shù)據(jù)緩存策略O(shè)SCache
可以解決的問題:
1)信息系統(tǒng)中需要處理的基礎(chǔ)數(shù)據(jù)的內(nèi)容短時(shí)間內(nèi)是不會(huì)發(fā)生變化的,但是在一個(gè)相對(duì)長(zhǎng)一些的時(shí)間里,它卻可能是動(dòng)態(tài)增加或者減少的。
2)統(tǒng)計(jì)報(bào)表是一個(gè)周期性的工作,可能是半個(gè)月、一個(gè)月或者更長(zhǎng)的時(shí)間才會(huì)需要更新一次,然而統(tǒng)計(jì)報(bào)表通常是圖形顯示或者是生成pdf、word、excel等格式的文件,這些圖形內(nèi)容、文件的生成通常需要消耗很多的系統(tǒng)資源,給系統(tǒng)運(yùn)行造成很大的負(fù)擔(dān)。
OSCache是OpenSymphony組織提供的一個(gè)J2EE架構(gòu)中Web應(yīng)用層的緩存技術(shù)實(shí)現(xiàn)組件。OSCache支持對(duì)部分頁(yè)面內(nèi)容或者對(duì)頁(yè)面級(jí)的響應(yīng)內(nèi)容進(jìn)行緩存,編程者可以根據(jù)不同的需求、不同的環(huán)境選擇不同的緩存級(jí)別。可以使用內(nèi)存、硬盤空間、同時(shí)使用內(nèi)存和硬盤或者提供自己的其他資源(需要自己提供適配器)作為緩存區(qū)。
使用步驟:
1. 下載、解壓縮OSCache
請(qǐng)到OSCache的主頁(yè)http://www.opensymphony.com/oscache/download.html下載Oscache的最新版本,作者下載的是OSCache的最新穩(wěn)定版本2.0。
將下載后的。Zip文件解壓縮到c:\oscache(后面的章節(jié)中將使用%OSCache_Home%來表示這個(gè)目錄)目錄下
2. 新建立一個(gè)web應(yīng)用
3. 將主要組件%OSCache_Home%\oscache.jar放入WEB-INF\lib目錄
4. commons-logging.jar、commons-collections.jar的處理
• OSCache組件用Jakarta Commons Logging來處理日志信息,所以需要commons-logging.jar的支持,請(qǐng)將%OSCache_Home%\lib\core\commons-logging.jar放入classpath(通常意味著將這個(gè)文件放入WEB-INF\lib目錄)
• 如果使用JDK1.3,請(qǐng)將%OSCache_Home%\lib\core\commons-collections.jar放入classpath,如果使用JDK1.4或者以上版本,則不需要了
5. 將oscache.properties、oscache.tld放入WEB-INF\class目錄
• %OSCache_Home%\oscache.properties包含了對(duì)OSCache運(yùn)行特征值的設(shè)置信息
• %OSCache_Home%\oscache.tld包含了OSCache提供的標(biāo)簽庫(kù)的定義內(nèi)容
6. 修改web.xml文件
在web.xml文件中增加下面的內(nèi)容,增加對(duì)OSCache提供的taglib的支持:
<taglib><taglib-uri>oscache</taglib-uri><taglib-location>/WEB-INF/classes/oscache.tld</taglib-location></taglib>
7.最簡(jiǎn)單的cache標(biāo)簽用法
使用默認(rèn)的關(guān)鍵字來標(biāo)識(shí)cache內(nèi)容,超時(shí)時(shí)間是默認(rèn)的3600秒
<cache:cache><%//自己的JSP代碼內(nèi)容%></cache:cache>
8. 緩存單個(gè)文件
在OSCache組件中提供了一個(gè)CacheFilter用于實(shí)現(xiàn)頁(yè)面級(jí)的緩存,主要用于對(duì)web應(yīng)用中的某些動(dòng)態(tài)頁(yè)面進(jìn)行緩存,尤其是那些需要生成pdf格式文件/報(bào)表、圖片文件等的頁(yè)面,不僅減少了數(shù)據(jù)庫(kù)的交互、減少數(shù)據(jù)庫(kù)服務(wù)器的壓力,而且對(duì)于減少web服務(wù)器的性能消耗有很顯著的效果。
修改web.xml,增加如下內(nèi)容,確定對(duì)/testContent.jsp頁(yè)面進(jìn)行緩存。
<filter> <filter-name>CacheFilter</filter-name><filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class></filter><filter-mapping><filter-name>CacheFilter</filter-name><!-對(duì)/testContent.jsp頁(yè)面內(nèi)容進(jìn)行緩存--> <url-pattern>/testContent.jsp</url-pattern></filter-mapping>
另一篇:
很多人對(duì)二級(jí)緩存都不太了解,或者是有錯(cuò)誤的認(rèn)識(shí),我一直想寫一篇文章介紹一下hibernate的二級(jí)緩存的,今天終于忍不住了。
我的經(jīng)驗(yàn)主要來自hibernate2.1版本,基本原理和3.0、3.1是一樣的,請(qǐng)?jiān)徫业念B固不化。
hibernate的session提供了一級(jí)緩存,每個(gè)session,對(duì)同一個(gè)id進(jìn)行兩次load,不會(huì)發(fā)送兩條sql給數(shù)據(jù)庫(kù),但是session關(guān)閉的時(shí)候,一級(jí)緩存就失效了。
二級(jí)緩存是SessionFactory級(jí)別的全局緩存,它底下可以使用不同的緩存類庫(kù),比如ehcache、oscache等,需要設(shè)置hibernate.cache.provider_class,我們這里用ehcache,在2.1中就是
hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider
如果使用查詢緩存,加上
hibernate.cache.use_query_cache=true
緩存可以簡(jiǎn)單的看成一個(gè)Map,通過key在緩存里面找value。
Class的緩存
對(duì)于一條記錄,也就是一個(gè)PO來說,是根據(jù)ID來找的,緩存的key就是ID,value是POJO。無論list,load還是iterate,只要讀出一個(gè)對(duì)象,都會(huì)填充緩存。但是list不會(huì)使用緩存,而iterate會(huì)先取數(shù)據(jù)庫(kù)select id出來,然后一個(gè)id一個(gè)id的load,如果在緩存里面有,就從緩存取,沒有的話就去數(shù)據(jù)庫(kù)load。假設(shè)是讀寫緩存,需要設(shè)置:
<cache usage="read-write"/>
如果你使用的二級(jí)緩存實(shí)現(xiàn)是ehcache的話,需要配置ehcache.xml
<cache name="com.xxx.pojo.Foo" maxElementsInMemory="500" eternal="false" timeToLiveSeconds="7200" timeToIdleSeconds="3600" overflowToDisk="true" />
其中eternal表示緩存是不是永遠(yuǎn)不超時(shí),timeToLiveSeconds是緩存中每個(gè)元素(這里也就是一個(gè)POJO)的超時(shí)時(shí)間,如果eternal="false",超過指定的時(shí)間,這個(gè)元素就被移走了。timeToIdleSeconds是發(fā)呆時(shí)間,是可選的。當(dāng)往緩存里面put的元素超過500個(gè)時(shí),如果overflowToDisk="true",就會(huì)把緩存中的部分?jǐn)?shù)據(jù)保存在硬盤上的臨時(shí)文件里面。
每個(gè)需要緩存的class都要這樣配置。如果你沒有配置,hibernate會(huì)在啟動(dòng)的時(shí)候警告你,然后使用defaultCache的配置,這樣多個(gè)class會(huì)共享一個(gè)配置。
當(dāng)某個(gè)ID通過hibernate修改時(shí),hibernate會(huì)知道,于是移除緩存。
這樣大家可能會(huì)想,同樣的查詢條件,第一次先list,第二次再iterate,就可以使用到緩存了。實(shí)際上這是很難的,因?yàn)槟銦o法判斷什么時(shí)候是第一次,而且每次查詢的條件通常是不一樣的,假如數(shù)據(jù)庫(kù)里面有100條記錄,id從1到100,第一次list的時(shí)候出了前50個(gè)id,第二次iterate的時(shí)候卻查詢到30至70號(hào)id,那么30-50是從緩存里面取的,51到70是從數(shù)據(jù)庫(kù)取的,共發(fā)送1+20條sql。所以我一直認(rèn)為iterate沒有什么用,總是會(huì)有1+N的問題。
(題外話:有說法說大型查詢用list會(huì)把整個(gè)結(jié)果集裝入內(nèi)存,很慢,而iterate只select id比較好,但是大型查詢總是要分頁(yè)查的,誰也不會(huì)真的把整個(gè)結(jié)果集裝進(jìn)來,假如一頁(yè)20條的話,iterate共需要執(zhí)行21條語(yǔ)句,list雖然選擇若干字段,比iterate第一條select id語(yǔ)句慢一些,但只有一條語(yǔ)句,不裝入整個(gè)結(jié)果集hibernate還會(huì)根據(jù)數(shù)據(jù)庫(kù)方言做優(yōu)化,比如使用mysql的limit,整體看來應(yīng)該還是list快。)
如果想要對(duì)list或者iterate查詢的結(jié)果緩存,就要用到查詢緩存了
查詢緩存
首先需要配置hibernate.cache.use_query_cache=true
如果用ehcache,配置ehcache.xml,注意hibernate3.0以后不是net.sf的包名了
<cache name="net.sf.hibernate.cache.StandardQueryCache"
maxElementsInMemory="50" eternal="false" timeToIdleSeconds="3600"
timeToLiveSeconds="7200" overflowToDisk="true"/>
<cache name="net.sf.hibernate.cache.UpdateTimestampsCache"
maxElementsInMemory="5000" eternal="true" overflowToDisk="true"/>
然后
query.setCacheable(true);//激活查詢緩存
query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion,可選
第二行指定要使用的cacheRegion是myCacheRegion,即你可以給每個(gè)查詢緩存做一個(gè)單獨(dú)的配置,使用setCacheRegion來做這個(gè)指定,需要在ehcache.xml里面配置它:
<cache name="myCacheRegion" maxElementsInMemory="10" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200" overflowToDisk="true" />
如果省略第二行,不設(shè)置cacheRegion的話,那么會(huì)使用上面提到的標(biāo)準(zhǔn)查詢緩存的配置,也就是net.sf.hibernate.cache.StandardQueryCache
對(duì)于查詢緩存來說,緩存的key是根據(jù)hql生成的sql,再加上參數(shù),分頁(yè)等信息(可以通過日志輸出看到,不過它的輸出不是很可讀,最好改一下它的代碼)。
比如hql:
from Cat c where c.name like ?
生成大致如下的sql:
select * from cat c where c.name like ?
參數(shù)是"tiger%",那么查詢緩存的key*大約*是這樣的字符串(我是憑記憶寫的,并不精確,不過看了也該明白了):
select * from cat c where c.name like ? , parameter:tiger%
這樣,保證了同樣的查詢、同樣的參數(shù)等條件下具有一樣的key。
現(xiàn)在說說緩存的value,如果是list方式的話,value在這里并不是整個(gè)結(jié)果集,而是查詢出來的這一串ID。也就是說,不管是list方法還是iterate方法,第一次查詢的時(shí)候,它們的查詢方式很它們平時(shí)的方式是一樣的,list執(zhí)行一條sql,iterate執(zhí)行1+N條,多出來的行為是它們填充了緩存。但是到同樣條件第二次查詢的時(shí)候,就都和iterate的行為一樣了,根據(jù)緩存的key去緩存里面查到了value,value是一串id,然后在到class的緩存里面去一個(gè)一個(gè)的load出來。這樣做是為了節(jié)約內(nèi)存。
可以看出來,查詢緩存需要打開相關(guān)類的class緩存。list和iterate方法第一次執(zhí)行的時(shí)候,都是既填充查詢緩存又填充class緩存的。
這里還有一個(gè)很容易被忽視的重要問題,即打開查詢緩存以后,即使是list方法也可能遇到1+N的問題!相同條件第一次list的時(shí)候,因?yàn)椴樵兙彺嬷姓也坏剑还躢lass緩存是否存在數(shù)據(jù),總是發(fā)送一條sql語(yǔ)句到數(shù)據(jù)庫(kù)獲取全部數(shù)據(jù),然后填充查詢緩存和class緩存。但是第二次執(zhí)行的時(shí)候,問題就來了,如果你的class緩存的超時(shí)時(shí)間比較短,現(xiàn)在class緩存都超時(shí)了,但是查詢緩存還在,那么list方法在獲取id串以后,將會(huì)一個(gè)一個(gè)去數(shù)據(jù)庫(kù)load!因此,class緩存的超時(shí)時(shí)間一定不能短于查詢緩存設(shè)置的超時(shí)時(shí)間!如果還設(shè)置了發(fā)呆時(shí)間的話,保證class緩存的發(fā)呆時(shí)間也大于查詢的緩存的生存時(shí)間。這里還有其他情況,比如class緩存被程序強(qiáng)制evict了,這種情況就請(qǐng)自己注意了。
另外,如果hql查詢包含select字句,那么查詢緩存里面的value就是整個(gè)結(jié)果集了。
當(dāng)hibernate更新數(shù)據(jù)庫(kù)的時(shí)候,它怎么知道更新哪些查詢緩存呢?
hibernate在一個(gè)地方維護(hù)每個(gè)表的最后更新時(shí)間,其實(shí)也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的緩存配置里面。
當(dāng)通過hibernate更新的時(shí)候,hibernate會(huì)知道這次更新影響了哪些表。然后它更新這些表的最后更新時(shí)間。每個(gè)緩存都有一個(gè)生成時(shí)間和這個(gè)緩存所查詢的表,當(dāng)hibernate查詢一個(gè)緩存是否存在的時(shí)候,如果緩存存在,它還要取出緩存的生成時(shí)間和這個(gè)緩存所查詢的表,然后去查找這些表的最后更新時(shí)間,如果有一個(gè)表在生成時(shí)間后更新過了,那么這個(gè)緩存是無效的。
可以看出,只要更新過一個(gè)表,那么凡是涉及到這個(gè)表的查詢緩存就失效了,因此查詢緩存的命中率可能會(huì)比較低。
Collection緩存
需要在hbm的collection里面設(shè)置
<cache usage="read-write"/>
假如class是Cat,collection叫children,那么ehcache里面配置
<cache name="com.xxx.pojo.Cat.children"
maxElementsInMemory="20" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200"
overflowToDisk="true" />
Collection的緩存和前面查詢緩存的list一樣,也是只保持一串id,但它不會(huì)因?yàn)檫@個(gè)表更新過就失效,一個(gè)collection緩存僅在這個(gè)collection里面的元素有增刪時(shí)才失效。
這樣有一個(gè)問題,如果你的collection是根據(jù)某個(gè)字段排序的,當(dāng)其中一個(gè)元素更新了該字段時(shí),導(dǎo)致順序改變時(shí),collection緩存里面的順序沒有做更新。
緩存策略
只讀緩存(read-only):沒有什么好說的
讀/寫緩存(read-write):程序可能要的更新數(shù)據(jù)
不嚴(yán)格的讀/寫緩存(nonstrict-read-write):需要更新數(shù)據(jù),但是兩個(gè)事務(wù)更新同一條記錄的可能性很小,性能比讀寫緩存好
事務(wù)緩存(transactional):緩存支持事務(wù),發(fā)生異常的時(shí)候,緩存也能夠回滾,只支持jta環(huán)境,這個(gè)我沒有怎么研究過
讀寫緩存和不嚴(yán)格讀寫緩存在實(shí)現(xiàn)上的區(qū)別在于,讀寫緩存更新緩存的時(shí)候會(huì)把緩存里面的數(shù)據(jù)換成一個(gè)鎖,其他事務(wù)如果去取相應(yīng)的緩存數(shù)據(jù),發(fā)現(xiàn)被鎖住了,然后就直接取數(shù)據(jù)庫(kù)查詢。
在hibernate2.1的ehcache實(shí)現(xiàn)中,如果鎖住部分緩存的事務(wù)發(fā)生了異常,那么緩存會(huì)一直被鎖住,直到60秒后超時(shí)。
不嚴(yán)格讀寫緩存不鎖定緩存中的數(shù)據(jù)。
使用二級(jí)緩存的前置條件
你的hibernate程序?qū)?shù)據(jù)庫(kù)有獨(dú)占的寫訪問權(quán),其他的進(jìn)程更新了數(shù)據(jù)庫(kù),hibernate是不可能知道的。你操作數(shù)據(jù)庫(kù)必需直接通過hibernate,如果你調(diào)用存儲(chǔ)過程,或者自己使用jdbc更新數(shù)據(jù)庫(kù),hibernate也是不知道的。hibernate3.0的大批量更新和刪除是不更新二級(jí)緩存的,但是據(jù)說3.1已經(jīng)解決了這個(gè)問題。
這個(gè)限制相當(dāng)?shù)募郑袝r(shí)候hibernate做批量更新、刪除很慢,但是你卻不能自己寫jdbc來優(yōu)化,很郁悶吧。
SessionFactory也提供了移除緩存的方法,你一定要自己寫一些JDBC的話,可以調(diào)用這些方法移除緩存,這些方法是:
void evict(Class persistentClass)
Evict all entries from the second-level cache.
void evict(Class persistentClass, Serializable id)
Evict an entry from the second-level cache.
void evictCollection(String roleName)
Evict all entries from the second-level cache.
void evictCollection(String roleName, Serializable id)
Evict an entry from the second-level cache.
void evictQueries()
Evict any query result sets cached in the default query cache region.
void evictQueries(String cacheRegion)
Evict any query result sets cached in the named query cache region.
不過我不建議這樣做,因?yàn)檫@樣很難維護(hù)。比如你現(xiàn)在用JDBC批量更新了某個(gè)表,有3個(gè)查詢緩存會(huì)用到這個(gè)表,用evictQueries(String cacheRegion)移除了3個(gè)查詢緩存,然后用evict(Class persistentClass)移除了class緩存,看上去好像完整了。不過哪天你添加了一個(gè)相關(guān)查詢緩存,可能會(huì)忘記更新這里的移除代碼。如果你的jdbc代碼到處都是,在你添加一個(gè)查詢緩存的時(shí)候,還知道其他什么地方也要做相應(yīng)的改動(dòng)嗎?
----------------------------------------------------
總結(jié):
不要想當(dāng)然的以為緩存一定能提高性能,僅僅在你能夠駕馭它并且條件合適的情況下才是這樣的。hibernate的二級(jí)緩存限制還是比較多的,不方便用jdbc可能會(huì)大大的降低更新性能。在不了解原理的情況下亂用,可能會(huì)有1+N的問題。不當(dāng)?shù)氖褂眠€可能導(dǎo)致讀出臟數(shù)據(jù)。
如果受不了hibernate的諸多限制,那么還是自己在應(yīng)用程序的層面上做緩存吧。
在越高的層面上做緩存,效果就會(huì)越好。就好像盡管磁盤有緩存,數(shù)據(jù)庫(kù)還是要實(shí)現(xiàn)自己的緩存,盡管數(shù)據(jù)庫(kù)有緩存,咱們的應(yīng)用程序還是要做緩存。因?yàn)榈讓拥木彺嫠⒉恢栏邔右眠@些數(shù)據(jù)干什么,只能做的比較通用,而高層可以有針對(duì)性的實(shí)現(xiàn)緩存,所以在更高的級(jí)別上做緩存,效果也要好些吧。
轉(zhuǎn)自:http://blog.ccidnet.com/blog-htm-do-showone-uid-44291-type-blog-itemid-125551.html
|