PS:原文轉(zhuǎn)自http://www.360doc.com/content/08/0131/10/51513_1017891.shtml,我加進(jìn)了很多個(gè)人的理解
Hibernate獲取數(shù)據(jù)的方式有不同的幾種,其與緩存結(jié)合使用的效果也不盡相同,而Hibernate中具體怎么使用緩存其實(shí)是我們很關(guān)心的一個(gè)問(wèn)題,直接涉及到性能方面。
緩存在Hibernate中主要有三個(gè)方面:一級(jí)緩存、二級(jí)緩存和查詢緩存
①一級(jí)緩存在Hibernate中對(duì)應(yīng)的為session范圍的緩存,也就是當(dāng)session關(guān)閉時(shí)緩存即被清除,一級(jí)緩存在Hibernate中是不可配置的
②二級(jí)緩存在Hibernate中對(duì)應(yīng)的為SessionFactory范圍的緩存,通常來(lái)講SessionFactory的生命周期和應(yīng)用的生命周期相同,所以可以看成是進(jìn)程緩存或集群緩存,二級(jí)緩存在Hibernate中是可以配置的,可以通過(guò)class-cache配置類粒度級(jí)別的緩存(class-cache在class中數(shù)據(jù)發(fā)生任何變化的情況下自動(dòng)更新),同時(shí)也可通過(guò)collection-cache配置集合粒度級(jí)別的緩存(collection-cache僅在collection中增加了元素或者刪除了元素的情況下才自動(dòng)更新,也就是當(dāng)collection中元素發(fā)生值的變化的情況下它是不會(huì)自動(dòng)更新的),跨session的緩存自然會(huì)帶來(lái)并發(fā)的訪問(wèn)題,這個(gè)時(shí)候相應(yīng)的就要根據(jù)應(yīng)用來(lái)設(shè)置緩存所采用的事務(wù)隔離級(jí)別,和數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別概念基本一樣,沒(méi)什么多介紹的
③查詢緩存在Hibernate同樣是可配置的,默認(rèn)是關(guān)閉的,可以通過(guò)設(shè)置cache.use_ query_cache為true來(lái)打開(kāi)查詢緩存。
根據(jù)緩存的通常實(shí)現(xiàn)策略,我們可以來(lái)理解Hibernate的這三種緩存,緩存的實(shí)現(xiàn)通過(guò)是通過(guò)key/value的Map方式來(lái)實(shí)現(xiàn),在Hibernate的一級(jí)、二級(jí)和查詢緩存也同樣如此。一級(jí)、二級(jí)緩存使用的key均為po的主鍵ID,value即為po實(shí)例對(duì)象,查詢緩存使用的則為查詢的條件(hql轉(zhuǎn)化而成的sql語(yǔ)句)、查詢的參數(shù)、查詢的頁(yè)數(shù),value有兩種情況,如果采用的是select po.property這樣的方式那么value為整個(gè)結(jié)果集,如采用的是from這樣的方式那么value為獲取的結(jié)果集中各po對(duì)象的主鍵ID,這樣的作用很明顯,節(jié)省內(nèi)存。
簡(jiǎn)單介紹完Hibernate的緩存后,再結(jié)合Hibernate的獲取數(shù)據(jù)方式來(lái)說(shuō)明緩存的具體使用方式,在Hibernate中獲取數(shù)據(jù)常用的方式主要有四種:Session.load、Session.get、Query.list、Query.iterator。
1、Session.load
在執(zhí)行session.load時(shí),Hibernate首先從當(dāng)前session的一級(jí)緩存中獲取id對(duì)應(yīng)的值,在獲取不到的情況下,將根據(jù)該對(duì)象是否配置了二級(jí)緩存來(lái)做相應(yīng)的處理,如配置了二級(jí)緩存,則從二級(jí)緩存中獲取id對(duì)應(yīng)的值,如仍然獲取不到則還需要根據(jù)是否配置了延遲加載來(lái)決定如何執(zhí)行,如未配置延遲加載則從數(shù)據(jù)庫(kù)中直接獲取,在從數(shù)據(jù)庫(kù)獲取到數(shù)據(jù)的情況下,Hibernate會(huì)相應(yīng)的填充一級(jí)緩存和二級(jí)緩存,如配置了延遲加載則直接返回一個(gè)代理類,只有在觸發(fā)代理類的調(diào)用時(shí)才進(jìn)行數(shù)據(jù)庫(kù)查詢的操作。
備注 load方法過(guò)程:一級(jí)緩存 ---> 二級(jí)緩存 ----> DB訪問(wèn) ---> 填充一級(jí)緩存[、二級(jí)緩存] / 返回一個(gè)代理類
在這樣的情況下我們就可以看到,在session一直打開(kāi)的情況下,要注意在適當(dāng)?shù)臅r(shí)候?qū)σ患?jí)緩存進(jìn)行刷新操作,通常是在該對(duì)象具有單向關(guān)聯(lián)維護(hù)的時(shí)候,在Hibernate中可以使用象session.clear、session.evict的方式來(lái)強(qiáng)制刷新一級(jí)緩存。 二級(jí)緩存則在數(shù)據(jù)發(fā)生任何變化(新增、更新、刪除)的情況下都會(huì)自動(dòng)的被更新。
備注:由于一級(jí)緩存是位于物理內(nèi)存空間的,畢竟大小有限。過(guò)多的對(duì)象緩存在這里會(huì)造成較大的壓力。適當(dāng)?shù)臅r(shí)候刷新緩存除了可以保證數(shù)據(jù)庫(kù)和內(nèi)存的狀態(tài)同步外,還可以移除不再用的對(duì)象,騰出空間來(lái)給后面的對(duì)象緩存使用。
2、Session.get
在執(zhí)行Session.get時(shí),和Session.load不同的就是在當(dāng)從緩存中獲取不到時(shí),直接從數(shù)據(jù)庫(kù)中獲取id對(duì)應(yīng)的值。
備注:load和get方法的另外一個(gè)區(qū)別就是:當(dāng)對(duì)象在DB中找不到時(shí),load會(huì)拋出異常,而get僅僅返回null
3、Query.list
在執(zhí)行Query.list時(shí),Hibernate的做法是首先檢查是否配置了查詢緩存,如配置了則從查詢緩存中查找key為查詢語(yǔ)句+查詢參數(shù)+分頁(yè)條件的值,如獲取不到則從數(shù)據(jù)庫(kù)中進(jìn)行獲取,從數(shù)據(jù)庫(kù)獲取到后Hibernate將會(huì)相應(yīng)的填充一級(jí)、二級(jí)和查詢緩存,如獲取到的為直接的結(jié)果集,則直接返回,如獲取到的為一堆id的值,則再根據(jù)id獲取相應(yīng)的值(Session.load),最后形成結(jié)果集返回,可以看到,在這樣的情況下,list也是有可能造成N次的查詢的。
查詢緩存在數(shù)據(jù)發(fā)生任何變化的情況下都會(huì)被自動(dòng)的清空。
備注:list()方法的SQL成本
①?zèng)]有配置查詢緩存或者緩存匹配不到時(shí),發(fā)出一次SQL查詢
②緩存的值為一組對(duì)象ID,則每個(gè)ID執(zhí)行一次Session.load操作。視乎緩存對(duì)象是否還在會(huì)再發(fā)出0~N次SQL查詢
③緩存的值為一組集合對(duì)象,則直接返回。此時(shí)不需要發(fā)出SQL查詢
由此可見(jiàn)list()方法的SQL成本從0~N次不等,具體的次數(shù)和一、二級(jí)緩存的對(duì)象生命周期時(shí)間設(shè)置、SQL語(yǔ)句寫(xiě)法有關(guān)。如果采用select po.value寫(xiě)法,那么SQL成本最低(0),如果采用from po寫(xiě)法,那么SQL成本從0~N不等。
雖然表明看采用select po.value的寫(xiě)法好像會(huì)更加高效,但是考慮到對(duì)象的大小,但一次性讀入時(shí)會(huì)造成大量的內(nèi)存空間浪費(fèi)。但是緩存ID又可能帶來(lái)N次額外的查詢消耗,應(yīng)該怎么辦呢?最好的方法就是設(shè)置一、二級(jí)緩存的對(duì)象生命周期時(shí)間比查詢緩存的生命周期長(zhǎng),這樣不至于在Hibernate對(duì)每個(gè)ID執(zhí)行Session.load方法時(shí)實(shí)體緩存都過(guò)期而被清空了。
另外一個(gè)要注意的是:如果查詢緩存引用的表在查詢后被修改了,那么不管緩存的數(shù)據(jù)是否有變,該查詢都會(huì)被清空而重新獲取。因?yàn)镠ibernate是靠比較每個(gè)表的修改時(shí)間和查詢緩存的填入時(shí)間來(lái)判斷表是否被修改了,它不會(huì)去判斷具體的內(nèi)容
4、Query.iterator
在執(zhí)行Query.iterator時(shí),和Query.list的不同的在于從數(shù)據(jù)庫(kù)獲取的處理上,Query.iterator向數(shù)據(jù)庫(kù)發(fā)起的是select id from這樣的語(yǔ)句,也就是它是先獲取符合查詢條件的id,之后在進(jìn)行iterator.next調(diào)用時(shí)才再次發(fā)起session.load的調(diào)用獲取實(shí)際的數(shù)據(jù)。
可見(jiàn),在擁有二級(jí)緩存并且查詢參數(shù)多變的情況下,Query.iterator會(huì)比Query.list更為高效。
備注:iterator的SQL成本
①最佳情況下,只有一次SQL查詢(ID查詢),其它的通過(guò)一、二級(jí)緩存可以得到
②最壞情況下,變成1+N次查詢(ID查詢 + 實(shí)體查詢)
這四種獲取數(shù)據(jù)的方式都各有適用的場(chǎng)合,要根據(jù)實(shí)際情況做相應(yīng)的決定,最好的方式無(wú)疑就是打開(kāi)show_sql選項(xiàng)看看執(zhí)行的情況來(lái)做分析,系統(tǒng)結(jié)構(gòu)上只用保證這種調(diào)整是容易實(shí)現(xiàn)的就好了,在cache這個(gè)方面的調(diào)整自然是非常的容易,只需要調(diào)整配置文件里的設(shè)置,而查詢的方式則可對(duì)外部進(jìn)行屏蔽,這樣要根據(jù)實(shí)際情況調(diào)整也非常容易。
備注:關(guān)于list()和iterator()的查詢成本比較
※沒(méi)有配置查詢緩存,或者緩存中沒(méi)有對(duì)應(yīng)的key :
list():執(zhí)行一次SQL查詢?nèi)咳〕?br />
iterator():執(zhí)行一次SQL語(yǔ)句先取出符合條件的所有對(duì)象ID,在迭代期間通過(guò)Session.load方法查詢(額外的0~N次查詢)
※緩存值是一組對(duì)象ID:
list():執(zhí)行Session.load方法查詢(額外的0~N次查詢)
iterator():執(zhí)行Session.load方法查詢(額外的0~N次查詢)
※緩存值是一組集合:
list():直接返回,沒(méi)有SQL查詢
iterator():直接返回,沒(méi)有SQL查詢
可見(jiàn)最大的區(qū)別在于從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)的地方,list雖然只有1次SQL查詢,但它是直接從物理數(shù)據(jù)文件中讀取。而iterator雖然是1+N次查詢,但后面的N次可能是從緩存中拿數(shù)據(jù),所以SQL語(yǔ)句次數(shù)相同,兼之iterator只取id,在緩存配置得當(dāng)?shù)那闆r下,iterator確實(shí)高效。但是如果緩存配置不當(dāng),那么iterator的后續(xù)next操作將不得不發(fā)出一次又一次的SQL查詢,反而大大比不上list。
從分頁(yè)查詢的情況來(lái)看,用iterator()的效率反而不及l(fā)ist():因?yàn)槊宽?yè)的對(duì)象的id都是不同的。iterator()使用Session.load時(shí)總是讀取不到實(shí)體緩存(一級(jí)、二級(jí)緩存)的值,只能再發(fā)出一次SQL查詢。
posted on 2010-03-15 11:00
Paul Lin 閱讀(4091)
評(píng)論(0) 編輯 收藏 所屬分類:
J2EE 框架