Hibernate緩存一直比較難掌握,下面就分析和總結(jié)原因,相信你就會慢慢清楚了原來Hibernate緩存也是可以輕松掌握的,但前提要求大家必須跟著動手去驗(yàn)證一下,再用心體會,光看是沒有用的
一、hibernate一級緩存(Session 級別的緩存)目錄:
一、hibernate一級緩存(session 級別的緩存)
二、一級緩存特征及其應(yīng)用
三、管理一級緩存
四、Hibernate二級緩存(sessionFactory級別緩存)
五、總結(jié)
hibernate是一個線程對應(yīng)一個session,一個線程可以看成一個用戶。也就是說session級緩存(一級緩存)只能給一個線程用,別的線程用不了,一級緩存就是和線程綁定了。hibernate一級緩存生命周期很短,和session生命周期一樣,一級緩存也稱session級的緩存或事務(wù)級緩存。如果tb事務(wù)提交或回滾了,我們稱session就關(guān)閉了,生命周期結(jié)束了。
實(shí)驗(yàn)1:體驗(yàn)一級緩存:(動手做做)
//同一個session中,發(fā)出兩次load方法查詢Employee emp= (Employee )session.load(Employee .class, 1);System.out.PRintln( emp.getName());//不會發(fā)出查詢語句,load使用緩存emp = (Employee )session.load(Employee .class, 1);System.out.println(emp.getName());
第二次查詢第一次相同的數(shù)據(jù),第二次load方法就是從緩存里取數(shù)據(jù),不會發(fā)出sql語句到數(shù)據(jù)庫里查詢。
緩存主要是用于查詢 ,hibernate的很多方法都是首先從緩存中取數(shù)據(jù)如果沒有在從數(shù)據(jù)庫中獲取,以提升查詢效率如:get()/load()、iterate(),而且持久態(tài)的對象是會存儲在緩存中的。例如:先save保存實(shí)體對象,再用load方法查詢剛剛save的實(shí)體對象,則load方法不會發(fā)出sql語句到數(shù)據(jù)庫查詢的,而是到緩存里取數(shù)據(jù),因?yàn)閟ave方法也支持緩存.
二、一級緩存特征及其應(yīng)用:1.Session 級別的緩存,它同session邦定。它的生命周期和session相同。Session消毀,它也同時消毀;
2.兩個session 不能共享一級緩存,因它會伴隨session的生命周期的創(chuàng)建和消毀;
3.Session緩存是實(shí)體級別的緩存,就是只有在查詢對象級別的時候才使用,如果使用HQL和SQL是查詢屬性級別的,是不使用一級緩存的!切記!!!
4.iterate 查詢使用緩存,會發(fā)出查詢Id的SQL和HQL語句,但不會發(fā)出查實(shí)體的,它查詢完會把相應(yīng)的實(shí)體放到緩存里邊,一些實(shí)體查詢?nèi)绻彺胬镞呌校蛷木彺嬷胁樵儯€是會發(fā)出查詢id的SQL和HQL語句。如果緩存中沒有它會數(shù)據(jù)庫中查詢,然后將查詢到的實(shí)體一個一個放到緩存中去,所以會有N+1問題出現(xiàn)。
5.List()和iterate 查詢區(qū)別:(動手做做)
使用iterate,list查詢實(shí)體對象*N+1問題,在默認(rèn)情況下,使用query.iterate查詢,有可以能出現(xiàn)N+1問題
所謂的N+1是在查詢的時候發(fā)出了N+1條sql語句1:首先發(fā)出一條查詢對象id列表的sqlN:
根據(jù)id列表到緩存中查詢,如果緩存中不存在與之匹配的數(shù)據(jù),那么會根據(jù)id發(fā)出相應(yīng)的sql語句list和iterate的區(qū)別?
list每次都會發(fā)出sql語句,list會向緩存中放入數(shù)據(jù),而不利用緩存中的數(shù)據(jù)
iterate:在默認(rèn)情況下iterate利用緩存數(shù)據(jù),但如果緩存中不存在數(shù)據(jù)有可以能出現(xiàn)N+1問題
6.Get()和load(),iterate方法都會使用一級緩存,
Get與load的區(qū)別? (動手做做)
1. 對于get方法,hibernate會確認(rèn)一下該id對應(yīng)的數(shù)據(jù)是否存在,首先在session緩存中查找,然后在二級緩存中查找,還沒有就查詢數(shù)據(jù)庫,數(shù)據(jù)庫中沒有就返回null。
2. load方法加載實(shí)體對象的時候,根據(jù)映射文件上類級別的lazy屬性的配置(默認(rèn)為true),分情況討論:
(1)若為true,則首先在Session緩存中查找,看看該id對應(yīng)的對象是否存在,不存在則使用延遲加載,返回實(shí)體的代理類對象(該代理類為實(shí)體類的子類,由CGLIB動態(tài)生成)。等到具體使用該對象(除獲取OID以外)的時候,再查詢二級緩存和數(shù)據(jù)庫,若仍沒發(fā)現(xiàn)符合條件的記錄,則會拋出一個ObjectNotFoundException。
(2)若為false,就跟get方法查找順序一樣,只是最終若沒發(fā)現(xiàn)符合條件的記錄,則會拋出一個ObjectNotFoundException。
小結(jié):
1、get方法首先查詢session緩存,沒有的話查詢二級緩存,最后查詢數(shù)據(jù)庫;而load方法首先查詢session緩存,沒有就創(chuàng)建代理,實(shí)際使用數(shù)據(jù)時才查詢二級緩存和數(shù)據(jù)庫
2、如果未能發(fā)現(xiàn)符合條件的記錄,get方法返回null,而load方法會拋出一個ObjectNotFoundException。
3、load使用代理延遲加載數(shù)據(jù),而get方法往往返回有實(shí)體數(shù)據(jù)的對象
使用:
1、如果想對一個對象進(jìn)行增刪改查之類,該使用load方法,性能提高,可以使用代理對象,省去了一次和數(shù)據(jù)庫交互的機(jī)會,當(dāng)真正用到該對象的屬性時,才跟數(shù)據(jù)庫交互
2、如果你想加載一個對象使用它的屬性,該使用get方法
7.hiberate3 session 存儲過程如下:
例如 object 對象Session.save(object);
這時候不會把數(shù)據(jù)放到數(shù)據(jù)庫,會先放到session緩存中去,數(shù)據(jù)庫中沒有相應(yīng)記錄,session.flush();才發(fā)SQL和HQL語句,數(shù)據(jù)庫中有了相應(yīng)記錄,
但是數(shù)據(jù)庫用select查不到,這是跟數(shù)據(jù)庫事物級別有關(guān)系。
Session.beginTrransaction()。commit();
事物提交后可以查詢到了。
Session.flush()語句但是為什么不寫呢,因?yàn)閏ommit()會默認(rèn)調(diào)用flush();
三、管理一級緩存無論何時,當(dāng)你給save()、update()或 saveOrUpdate()方法傳遞一個對象時,或使用load()、 get()、list()、iterate() 或scroll()方法獲得一個對象時, 該對象都將被加入到Session的內(nèi)部緩存中。當(dāng)隨后flush()方法被調(diào)用時,對象的狀態(tài)會和數(shù)據(jù)庫取得同步。 如果你不希望此同步操作發(fā)生,或者你正處理大量對象、需要對有效管理內(nèi)存時,你可以調(diào)用evict() 方法,從一級緩存中去掉這些對象及其集合。
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll();while ( cats.next() ) { Cat cat = (Cat) cats.get(0); doSomethingWithACat(cat); sess.evict(cat);}Session還提供了一個contains()方法,用來判斷某個實(shí)例是否處于當(dāng)前session的緩存中。如若要把所有的對象從session緩存中徹底清除,則需要調(diào)用Session.clear()。
四、Hibernate二級緩存(sessionFactory級別緩存)二級緩存需要sessionFactory來管理,它是進(jìn)初級的緩存,所有人都可以使用,它是共享的。Hibernate二級緩存支持對象緩存、集合緩存、查詢結(jié)果集緩存,對于查詢結(jié)果集緩存可選。
二級緩存比較復(fù)雜,一般用第三方產(chǎn)品。hibernate提供了一個簡單實(shí)現(xiàn),用Hashtable做的,只能作為我們的測試使用,商用還是需要第三方產(chǎn)品。
幾種優(yōu)秀緩存方案:
1、Memcached 分布式緩存系統(tǒng) 2、JBOSS CACHE 3 、EhCache Ehcache 2.1起提供了針對Hibernate的JTA支持。 4、Infinispan 開源數(shù)據(jù)網(wǎng)格平臺
使用緩存,肯定是長時間不改變的數(shù)據(jù),如果經(jīng)常變化的數(shù)據(jù)放到緩存里就沒有太大意義了。因?yàn)榻?jīng)常變化,還是需要經(jīng)常到數(shù)據(jù)庫里查詢,那就沒有必要用緩存了。hibernate做了一些優(yōu)化,和一些第三方的緩存產(chǎn)品做了集成。這里采用EHCache緩存產(chǎn)品。
和EHCache二級緩存產(chǎn)品集成:EHCache的jar文件在hibernate的lib里,我們還需要設(shè)置一系列的緩存使用策略,需要一個配置文件ehcache.xml來配置。
hibernate.cfg.xml 配置(動手做做)
<!-- 開啟二級緩存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 開啟查詢緩存 --> <property name="hibernate.cache.use_query_cache">true</property> <!-- 二級緩存區(qū)域名的前綴 --> <!--<property name="hibernate.cache.region_prefix">h3test</property>--> <!-- 高速緩存提供程序 第三方產(chǎn)品--> <property name="hibernate.cache.region.factory_class"> net.sf.ehcache.hibernate.EhCacheRegionFactory </property> <!-- 指定緩存配置文件位置 --> <property name="hibernate.cache.provider_configuration_file_resource_path"> ehcache.xml </property> <!-- 強(qiáng)制Hibernate以更人性化的格式將數(shù)據(jù)存入二級緩存 --> <property name="hibernate.cache.use_structured_entries">true</property> <!-- Hibernate將收集有助于性能調(diào)節(jié)的統(tǒng)計數(shù)據(jù) --> <property name="hibernate.generate_statistics">true</property>
ehcache配置(ehcache.xml)(動手做做)
<?xml version="1.0" encoding="UTF-8"?> <ehcache name="h3test"> <!--指定區(qū)域名--><defaultCache maxElementsInMemory="100" <!--緩存在內(nèi)存中的最大數(shù)目-->eternal="false" <!--緩存是否持久-->timeToIdleSeconds="1200" <!--當(dāng)緩存條目閑置n秒后銷毀-->timeToLiveSeconds="1200" <!--當(dāng)緩存條目存活n秒后銷毀-->overflowToDisk="false"> <!--硬盤溢出--></defaultCache> </ehcache>
實(shí)體只讀緩存
只讀緩存 read only,不須要鎖與事務(wù),因?yàn)榫彺孀詳?shù)據(jù)從數(shù)據(jù)庫加載后就不會改變。如果數(shù)據(jù)是只讀的,例如引用數(shù)據(jù),那么總是使用“read-only”策略,因?yàn)樗亲詈唵巍⒆罡咝У牟呗裕彩羌喊踩牟呗浴J切阅艿谝坏牟呗?/p>
<hibernate-mapping> <class name="com.ljb.entity.Voucher" table="Voucher"> <cache usage="read-only"/> …… </hibernate-mapping>
二級緩存測試代碼(動手做做)
Session session1 = sf.openSession(); Transaction t1 = session1.beginTransaction(); //確保數(shù)據(jù)庫中有標(biāo)識符為1的Voucher Voucher voucher = (Vocher) session1.get(Vocher.class, 1); //如果修改將報錯,只讀緩存不允許修改 //voucher.setName("aaa"); t1.commit(); session1.close();Session session2 = sf.openSession(); Transaction t2 = session2.beginTransaction(); voucher = (Vocher) session2.get(Vocher.class, 1); //在二級緩存中查找結(jié)果,不會產(chǎn)出sql語句,不操作數(shù)據(jù)庫t2.commit(); session2.close(); sf.close(); 只讀緩存不允許更新,將報錯Can't write to a readonly object。允許新增,( 新增直接添加到二級緩存)
讀寫緩存 read write
對緩存的更新發(fā)生在數(shù)據(jù)庫事務(wù)完成后。緩存需要支持鎖。在一個事務(wù)中更新數(shù)據(jù)庫,在這個事務(wù)成功完成后更新緩存,并釋放鎖。鎖只是一種特定的緩存值失效表述方式,在它獲得新數(shù)據(jù)庫值前阻止其他事務(wù)讀寫緩存。那些事務(wù)會轉(zhuǎn)而直接讀取數(shù)據(jù)庫
實(shí)體讀/寫緩存
<hibernate-mapping> <class name="com.ljb.entity.Voucher" table="Voucher"> <cache usage="read-write"/> …… </hibernate-mapping>
二級緩存測試代碼
Session session1 = sf.openSession(); Transaction t1 = session1.beginTransaction(); //確保數(shù)據(jù)庫中有標(biāo)識符為1的Voucher Voucher voucher = (Vocher) session1.get(Vocher.class, 1); //如果修改將報錯,只讀緩存不允許修改 voucher.setName("aaa"); t1.commit(); session1.close(); Session session2 = sf.openSession(); Transaction t2 = session2.beginTransaction(); voucher = (Vocher) session2.get(Vocher.class, 1); //該條目已經(jīng)被別的事務(wù)修改了,此時重新查詢一次數(shù)據(jù)庫 t2.commit(); session2.close(); sf.close(); 允許更新,更新后自動同步到緩存。允許新增,新增記錄后自動同步到緩存。保證read committed隔離級別及可重復(fù)讀隔離級別(通過時間戳實(shí)現(xiàn))整個過程加鎖,如果當(dāng)前事務(wù)的時間戳早于二級緩存中的條目的時間戳,說明該條目已經(jīng)被別的事務(wù)修改了,此時重新查詢一次數(shù)據(jù)庫,否則才使用緩存數(shù)據(jù),因此保證可重復(fù)讀隔離級別
非嚴(yán)格讀寫緩存 nonstrict read write
在一個事務(wù)中更新數(shù)據(jù)庫,在這個事務(wù)完成前就清除緩存,為了安全起見,無論事務(wù)成功與否,在事務(wù)完成后再次清除緩存。既不需要支持緩存鎖,也不需要支持事務(wù)。如果是緩存集群,“清除緩存”調(diào)用會讓所有副本都失效,這通常被稱為“拉(pull)”更新策略。如果你的數(shù)據(jù)讀很多或者很少有并發(fā)緩存訪問和更新,那么可以使用“nonstrict-read-write”策略。感謝它的輕量級“拉”更新策略,它通常是性能第二好的策略。
實(shí)體非嚴(yán)格讀/寫緩存
<hibernate-mapping> <class name="com.ljb.entity.Voucher" table="Voucher"> <cache usage="nonstrict-read-write"/> …… </hibernate-mapping>
測試代碼 略(我想大家會驗(yàn)證了)
驗(yàn)證結(jié)果
允許更新,更新后緩存失效,需再查詢一次。 允許新增,新增記錄自動加到二級緩存中。整個過程不加鎖,不保證。
事務(wù)緩存 transactional (一定要在JTA環(huán)境中)
對緩存和數(shù)據(jù)庫的更新被包裝在同一個JTA事務(wù)中,這樣緩存與數(shù)據(jù)庫總是保持同步的。數(shù)據(jù)庫和緩存都必須支持JTA。除非你真的想將緩存更新和數(shù)據(jù)庫更新放在一個JTA事務(wù)里,否則不要使用“transactional”策略,因?yàn)镴TA需要漫長的兩階段提交處理,這導(dǎo)致它基本是性能最差的策略。
需要特定緩存的支持和JTA事務(wù)支持,此處不演示。
集合緩存
演示讀/寫緩存示例,和之前實(shí)體緩存測試差不多,其他自測
<hibernate-mapping> <class name="cn.javass.h3test.model.UserModel" table="TBL_USER"> <cache usage="read-write" /> <set name="vouchers" cascade="all" inverse="true" lazy="false"> <cache usage="read-write"/> <key column="fk_user_id"/> <one-to-many class="cn.ljb.entity.Voucher"/> </set> </class> </hibernate-mapping>
SessionFactory sf = new Configuration().configure().buildSessionFactory(); Session session1 = sf.openSession(); Transaction t1 = session1.beginTransaction(); //確保數(shù)據(jù)庫中有標(biāo)識符為1的UserModel UserModel user = (UserModel) session1.get(UserModel.class, 1); user.getVouchers(); t1.commit(); session1.close(); Session session2 = sf.openSession(); Transaction t2 = session2.beginTransaction(); user = (UserModel) session2.get(UserModel.class, 1); user.getVouchers(); t2.commit(); session2.close(); sf.close();
測試結(jié)論:
和實(shí)體并發(fā)策略有相同含義; 但集合緩存只緩存集合元素的標(biāo)識符,在二級緩存中只存放相應(yīng)實(shí)體的標(biāo)識符,然后再通過標(biāo)識符去二級緩存查找相應(yīng)的實(shí)體最后組合為集合返回。
查詢緩存 (動手做做)
1、保證全局配置中有開啟了查詢緩存。
2、修改FarmModel.hbm.xml,添加如下紅色部分配置,表示實(shí)體緩存并讀/寫
<hibernate-mapping> <class name="com.lijb.entity.Voucher" table="voucher"> <cache usage="read-write"/> …… </hibernate-mapping>
3、測試代碼
SessionFactory sf = .new Configuration().configure().buildSessionFactory(); Session session1 = sf.openSession(); Transaction t1 = session1.beginTransaction(); Query query = session1.createQuery("fromVoucher"); //即使全局打開了查詢緩存,此處也是必須的 query.setCacheable(true); List<Voucher> voucherList = query.list(); t1.commit(); session1.close(); Session session2 = sf.openSession(); Transaction t2 = session2.beginTransaction(); query = session2.createQuery("from Voucher"); //即使全局打開了查詢緩存,此處也是必須的 query.setCacheable(true); voucherList = query.list(); t2.commit(); session2.close(); sf.close(); 結(jié)論:
和實(shí)體并發(fā)策略有相同含義; 和集合緩存類似,只緩存集合元素的標(biāo)識符,在二級緩存中只存放相應(yīng)實(shí)體的標(biāo)識符,然后再通過標(biāo)識符 去二級緩存查找相應(yīng)的實(shí)體最后組合為集合返回。
什么時候需要查詢緩存?
大多數(shù)時候無法從結(jié)果集高速緩存獲益。必須知道:每隔多久重復(fù)執(zhí)行同一查詢。對于那些查詢非常多但插入、刪除、更新非常少的應(yīng)用程序來說,查詢緩存可提升性能。但寫入多查詢少的沒有用,總失效。
管理二級緩存
對于二級緩存來說,在SessionFactory中定義了許多方法, 清除緩存中實(shí)例、整個類、集合實(shí)例或者整個集合。
sessionFactory.evict(Cat.class, catId); //evict a particular CatsessionFactory.evict(Cat.class); //evict all CatssessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittenssessionFactory.evictCollection("Cat.kittens"); //evict all kitten collectionssessionFactory.evictQueries()//evict all queries//CacheMode參數(shù)用于控制具體的Session如何與二級緩存進(jìn)行交互。//CacheMode.NORMAL - 從二級緩存中讀、寫數(shù)據(jù)。//CacheMode.GET - 從二級緩存中讀取數(shù)據(jù),僅在數(shù)據(jù)更新時對二級緩存寫數(shù)據(jù)。//CacheMode.PUT - 僅向二級緩存寫數(shù)據(jù),但不從二級緩存中讀數(shù)據(jù)。//CacheMode.REFRESH - 僅向二級緩存寫數(shù)據(jù),但不從二級緩存中讀數(shù)據(jù)。通過 hibernate.cache.use_minimal_puts的設(shè)置,強(qiáng)制二級緩存從數(shù)據(jù)庫中讀取數(shù)據(jù),刷新緩存內(nèi)容監(jiān)控二級緩存
如若需要查看二級緩存或查詢緩存區(qū)域的內(nèi)容,你可以使用統(tǒng)計(Statistics) API。通過sessionFactory.getStatistics();獲取Hibernate統(tǒng)計信息。此時,你必須手工打開統(tǒng)計選項(xiàng)。
hibernate.generate_statistics true hibernate.cache.use_structured_entries true五、總結(jié)
不要想當(dāng)然的以為緩存一定能提高性能,僅僅在你能夠駕馭它并且條件合適的情況下才是這樣的。hibernate的二級緩存限制還是比較多的,不方便用jdbc可能會大大的降低更新性能。在不了解原理的情況下亂用,可能會有1+N的問題。不當(dāng)?shù)氖褂眠€可能導(dǎo)致讀出臟數(shù)據(jù)。
如果受不了hibernate的諸多限制,那么還是自己在應(yīng)用程序的層面上做緩存吧。
在越高的層面上做緩存,效果就會越好。就好像盡管磁盤有緩存,數(shù)據(jù)庫還是要實(shí)現(xiàn)自己的緩存,盡管數(shù)據(jù)庫有緩存,咱們的應(yīng)用程序還是要做緩存。因?yàn)榈讓拥木彺嫠⒉恢栏邔右眠@些數(shù)據(jù)干什么,只能做的比較通用,而高層可以有針對性的實(shí)現(xiàn)緩存,所以在更高的級別上做緩存,效果也要好些。
作者:杰瑞教育出處:http://m.survivalescaperooms.com/jerehedu/本文版權(quán)歸煙臺杰瑞教育科技有限公司和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。新聞熱點(diǎn)
疑難解答