Hibernate緩衝應用的積累與總結,hibernate緩衝積累
Hibernate緩衝一直比較難掌握,下面就分析和總結原因,相信你就會慢慢清楚了原來Hibernate緩衝也是可以輕鬆掌握的,但前提要求大家必須跟著動手去驗證一下,再用心體會,光看是沒有用的
一、hibernate一級緩衝(Session 層級的緩衝)
hibernate是一個線程對應一個session,一個線程可以看成一個使用者。也就是說session級緩衝(一級緩衝)只能給一個線程用,別的線程用不了,一級緩衝就是和線程綁定了。hibernate一級緩衝生命週期很短,和session生命週期一樣,一級緩衝也稱session級的緩衝或事務級緩衝。如果tb事務提交或復原了,我們稱session就關閉了,生命週期結束了。
實驗1:體驗一級緩衝:(動手做做)
//同一個session中,發出兩次load方法查詢Employee emp= (Employee )session.load(Employee .class, 1);System.out.println( emp.getName());//不會發出查詢語句,load使用緩衝emp = (Employee )session.load(Employee .class, 1);System.out.println(emp.getName());
第二次查詢第一次相同的資料,第二次load方法就是從緩衝裡取資料,不會發出sql語句到資料庫裡查詢。
緩衝主要是用於查詢 ,hibernate的很多方法都是首先從緩衝中取資料如果沒有在從資料庫中擷取,以提升查詢效率如:get()/load()、iterate(),而且持久態的對象是會儲存在緩衝中的。例如:先save儲存實體物件,再用load方法查詢剛剛save的實體物件,則load方法不會發出sql語句到資料庫查詢的,而是到緩衝裡取資料,因為save方法也支援緩衝.
二、一級緩衝特徵及其應用:
1.Session 層級的緩衝,它同session邦定。它的生命週期和session相同。Session消毀,它也同時消毀;
2.兩個session 不能共用一級緩衝,因它會伴隨session的生命週期的建立和消毀;
3.Session緩衝是實體層級的緩衝,就是只有在查詢對象層級的時候才使用,如果使用HQL和SQL是查詢屬性層級的,是不使用一級緩衝的!切記!!!
4.iterate 查詢使用緩衝,會發出查詢Id的SQL和HQL語句,但不會發出查實體的,它查詢完會把相應的實體放到緩衝裡邊,一些實體查詢如果緩衝裡邊有,就從緩衝中查詢,但還是會發出查詢id的SQL和HQL語句。如果緩衝中沒有它會資料庫中查詢,然後將查詢到的實體一個一個放到緩衝中去,所以會有N+1問題出現。
5.List()和iterate 查詢區別:(動手做做)
使用iterate,list查詢實體物件*N+1問題,在預設情況下,使用query.iterate查詢,有可以能出現N+1問題
所謂的N+1是在查詢的時候發出了N+1條sql語句1:首先發出一條查詢對象id列表的sqlN:
根據id列表到緩衝中查詢,如果緩衝中不存在與之匹配的資料,那麼會根據id發出相應的sql語句list和iterate的區別?
list每次都會發出sql語句,list會向緩衝中放入資料,而不利用緩衝中的資料
iterate:在預設情況下iterate利用快取資料,但如果緩衝中不存在資料有可以能出現N+1問題
6.Get()和load(),iterate方法都會使用一級緩衝,
Get與load的區別? (動手做做)
1. 對於get方法,hibernate會確認一下該id對應的資料是否存在,首先在session緩衝中尋找,然後在二級緩衝中尋找,還沒有就查詢資料庫,資料庫中沒有就返回null。
2. load方法載入實體物件的時候,根據對應檔上類層級的lazy屬性的配置(預設為true),分情況討論:
(1)若為true,則首先在Session緩衝中尋找,看看該id對應的對象是否存在,不存在則使用消極式載入,返回實體的代理類對象(該代理類為實體類的子類,由CGLIB動態產生)。等到具體使用該對象(除擷取OID以外)的時候,再查詢二級緩衝和資料庫,若仍沒發現合格記錄,則會拋出一個ObjectNotFoundException。
(2)若為false,就跟get方法尋找順序一樣,只是最終若沒發現合格記錄,則會拋出一個ObjectNotFoundException。
小結:
1、get方法首先查詢session緩衝,沒有的話查詢二級緩衝,最後查詢資料庫;而load方法首先查詢session緩衝,沒有就建立代理,實際使用資料時才查詢二級緩衝和資料庫
2、如果未能發現合格記錄,get方法返回null,而load方法會拋出一個ObjectNotFoundException。
3、load使用代理消極式載入資料,而get方法往往返回有實體資料的對象
使用:
1、如果想對一個對象進行增刪改查之類,該使用load方法,效能提高,可以使用代理對象,省去了一次和資料庫互動的機會,當真正用到該對象的屬性時,才跟資料庫互動
2、如果你想載入一個對象使用它的屬性,該使用get方法
7.hiberate3 session 預存程序如下:
例如 object 對象Session.save(object);
這時候不會把資料放到資料庫,會先放到session緩衝中去,資料庫中沒有相應記錄,session.flush();才發SQL和HQL語句,資料庫中有了相應記錄,
但是資料庫用select查不到,這是跟資料庫事物層級有關係。
Session.beginTrransaction()。commit();
事物提交後可以查詢到了。
Session.flush()語句但是為什麼不寫呢,因為commit()會預設調用flush();
三、管理一級緩衝
無論何時,當你給save()、update()或 saveOrUpdate()方法傳遞一個對象時,或使用load()、 get()、list()、iterate() 或scroll()方法獲得一個對象時, 該對象都將被加入到Session的內部緩衝中。當隨後flush()方法被調用時,對象的狀態會和資料庫取得同步。 如果你不希望此同步操作發生,或者你正處理大量對象、需要對有效管理記憶體時,你可以調用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()方法,用來判斷某個執行個體是否處於當前session的緩衝中。如若要把所有的對象從session緩衝中徹底清除,則需要調用Session.clear()。
四、Hibernate二級緩衝(sessionFactory層級緩衝)
二級緩衝需要sessionFactory來管理,它是進初級的緩衝,所有人都可以使用,它是共用的。Hibernate二級緩衝支援對象緩衝、集合緩衝、查詢結果集緩衝,對於查詢結果集緩衝可選。
二級緩衝比較複雜,一般用第三方產品。hibernate提供了一個簡單實現,用Hashtable做的,只能作為我們的測試使用,商用還是需要第三方產品。
幾種優秀緩衝方案:
1、Memcached 分布式緩衝系統 2、JBOSS CACHE 3 、EhCache Ehcache 2.1起提供了針對Hibernate的JTA支援。 4、Infinispan 開來源資料網格平台
使用緩衝,肯定是長時間不改變的資料,如果經常變化的資料放到緩衝裡就沒有太大意義了。因為經常變化,還是需要經常到資料庫裡查詢,那就沒有必要用緩衝了。hibernate做了一些最佳化,和一些第三方的緩衝產品做了整合。這裡採用EHCache緩衝產品。
和EHCache二級緩衝產品整合:EHCache的jar檔案在hibernate的lib裡,我們還需要設定一系列的緩衝使用原則,需要一個設定檔ehcache.xml來配置。
hibernate.cfg.xml 配置(動手做做)
<!-- 開啟二級緩衝 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 開啟查詢快取 --> <property name="hibernate.cache.use_query_cache">true</property> <!-- 二級快取區域名的首碼 --> <!--<property name="hibernate.cache.region_prefix">h3test</property>--> <!-- 快取提供者 第三方產品--> <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> <!-- 強制Hibernate以更人性化的格式將資料存入二級緩衝 --> <property name="hibernate.cache.use_structured_entries">true</property> <!-- Hibernate將收集有助於效能調節的統計資料 --> <property name="hibernate.generate_statistics">true</property>
ehcache配置(ehcache.xml)(動手做做)
<?xml version="1.0" encoding="UTF-8"?> <ehcache name="h3test"> <!--指定地區名--><defaultCache maxElementsInMemory="100" <!--緩衝在記憶體中的最大數目-->eternal="false" <!--緩衝是否持久-->timeToIdleSeconds="1200" <!--當緩衝條目閑置n秒後銷毀-->timeToLiveSeconds="1200" <!--當緩衝條目存活n秒後銷毀-->overflowToDisk="false"> <!--硬碟溢出--></defaultCache> </ehcache>
實體唯讀緩衝
唯讀緩衝 read only,不須要鎖與事務,因為緩衝自資料從資料庫載入後就不會改變。如果資料是唯讀,例如引用資料,那麼總是使用“read-only”策略,因為它是最簡單、最高效的策略,也是叢集安全的策略。是效能第一的策略
<hibernate-mapping> <class name="com.ljb.entity.Voucher" table="Voucher"> <cache usage="read-only"/> …… </hibernate-mapping>
二級緩衝測試代碼(動手做做)
Session session1 = sf.openSession(); Transaction t1 = session1.beginTransaction(); //確保資料庫中有標識符為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); //在二級緩衝中尋找結果,不會產出sql語句,不操作資料庫t2.commit(); session2.close(); sf.close();
唯讀緩衝不允許更新,將報錯Can't write to a readonly object。允許新增,( 新增直接添加到二級緩衝)
讀寫緩衝 read write
對緩衝的更新發生在資料庫事務完成後。緩衝需要支援鎖。在一個事務中更新資料庫,在這個事務成功完成後更新緩衝,並釋放鎖。鎖只是一種特定的緩衝值失效表述方式,在它獲得新資料庫值前阻止其他事務讀寫緩衝。那些事務會轉而直接讀取資料庫
實體讀/寫緩衝
<hibernate-mapping> <class name="com.ljb.entity.Voucher" table="Voucher"> <cache usage="read-write"/> …… </hibernate-mapping>
二級緩衝測試代碼
Session session1 = sf.openSession(); Transaction t1 = session1.beginTransaction(); //確保資料庫中有標識符為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); //該條目已經被別的事務修改了,此時重新查詢一次資料庫 t2.commit(); session2.close(); sf.close();
允許更新,更新後自動同步到緩衝。允許新增,新增記錄後自動同步到緩衝。保證read committed隔離等級及可重複讀隔離等級(通過時間戳記實現)整個過程加鎖,如果當前事務的時間戳記早於二級緩衝中的條目的時間戳記,說明該條目已經被別的事務修改了,此時重新查詢一次資料庫,否則才使用快取資料,因此保證可重複讀隔離等級
非嚴格讀寫緩衝 nonstrict read write
在一個事務中更新資料庫,在這個事務完成前就清除緩衝,為了安全起見,無論事務成功與否,在事務完成後再次清除緩衝。既不需要支援緩衝鎖,也不需要支援事務。如果是緩衝叢集,“清除緩衝”調用會讓所有副本都失效,這通常被稱為“拉(pull)”更新策略。如果你的資料讀很多或者很少有並發緩衝訪問和更新,那麼可以使用“nonstrict-read-write”策略。感謝它的輕量級“拉”更新策略,它通常是效能第二好的策略。
實體非嚴格讀/寫緩衝
<hibernate-mapping> <class name="com.ljb.entity.Voucher" table="Voucher"> <cache usage="nonstrict-read-write"/> …… </hibernate-mapping>
測試代碼 略(我想大家會驗證了)
驗證結果
允許更新,更新後緩衝失效,需再查詢一次。 允許新增,新增記錄自動加到二級緩衝中。整個過程不加鎖,不保證。
事務緩衝 transactional (一定要在JTA環境中)
對緩衝和資料庫的更新被封裝在同一個JTA事務中,這樣緩衝與資料庫總是保持同步的。資料庫和緩衝都必須支援JTA。除非你真的想將緩衝更新和資料庫更新放在一個JTA事務裡,否則不要使用“transactional”策略,因為JTA需要漫長的兩階段交易認可處理,這導致它基本是效能最差的策略。
需要特定緩衝的支援和JTA事務支援,此處不示範。
集合緩衝
示範讀/寫緩衝樣本,和之前實體緩衝測試差不多,其他自測
<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(); //確保資料庫中有標識符為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();
測試結論:
和實體並發策略有相同含義; 但集合緩衝只緩衝集合元素的標識符,在二級緩衝中只存放相應實體的標識符,然後再通過標識符去二級緩衝尋找相應的實體最後組合為集合返回。
查詢快取 (動手做做)
1、保證全域配置中有開啟了查詢快取。
2、修改FarmModel.hbm.xml,添加如下紅色部分配置,表示實體緩衝並讀/寫
<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();
結論:
和實體並發策略有相同含義; 和集合緩衝類似,只緩衝集合元素的標識符,在二級緩衝中只存放相應實體的標識符,然後再通過標識符 去二級緩衝尋找相應的實體最後組合為集合返回。
什麼時候需要查詢快取?
大多數時候無法從結果集快取獲益。必須知道:每隔多久重複執行同一查詢。對於那些查詢非常多但插入、刪除、更新非常少的應用程式來說,查詢快取可提升效能。但寫入多查詢少的沒有用,總失效。
管理二級緩衝
對於二級緩衝來說,在SessionFactory中定義了許多方法, 清除緩衝中執行個體、整個類、集合執行個體或者整個集合。
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參數用於控制具體的Session如何與二級緩衝進行互動。//CacheMode.NORMAL - 從二級緩衝中讀、寫資料。//CacheMode.GET - 從二級緩衝中讀取資料,僅在資料更新時對二級緩衝寫資料。//CacheMode.PUT - 僅向二級緩衝寫資料,但不從二級緩衝中讀資料。//CacheMode.REFRESH - 僅向二級緩衝寫資料,但不從二級緩衝中讀資料。通過 hibernate.cache.use_minimal_puts的設定,強制二級緩衝從資料庫中讀取資料,重新整理緩衝內容
監控二級緩衝
如若需要查看二級緩衝或查詢快取地區的內容,你可以使用統計(Statistics) API。通過sessionFactory.getStatistics();擷取Hibernate統計資訊。此時,你必須手工開啟統計選項。
hibernate.generate_statistics true hibernate.cache.use_structured_entries true
五、總結
不要想當然的以為緩衝一定能提高效能,僅僅在你能夠駕馭它並且條件合適的情況下才是這樣的。hibernate的二級緩衝限制還是比較多的,不方便用jdbc可能會大大的降低更新效能。在不瞭解原理的情況下亂用,可能會有1+N的問題。不當的使用還可能導致讀出髒資料。
如果受不了hibernate的諸多限制,那麼還是自己在應用程式的層面上做緩衝吧。
在越高的層面上做緩衝,效果就會越好。就好像儘管磁碟有緩衝,資料庫還是要實現自己的緩衝,儘管資料庫有緩衝,咱們的應用程式還是要做緩衝。因為底層的緩衝它並不知道高層要用這些資料幹什麼,只能做的比較通用,而高層可以有針對性的實現緩衝,所以在更高的層級上做緩衝,效果也要好些。