【大话Hibernate】hibernate缓存详解
為什么要用hibernate緩存?
hibernate是一個持久層框架,經常訪問物理數據庫。為了降低應用程序對物理數據源訪問的次數,從而提高應用程序的運行性能,我們想到使用hibernate緩存機制。緩存內的數據是對物理數據源中的數據的復制,應用程序在運行時從緩存讀寫數據,在特定的時刻或事件會同步緩存和物理數據源的數據。
hibernate緩存的原理
緩存的主要作用是查詢。
hibernate緩存包括三大類:hibernate一級緩存、hibernate二級緩存和hibernate查詢緩存。
一級緩存
一級緩存是hibernate自帶的,不受用戶干預。
hibernate是一個線程對應一個session,一個線程可以看成一個用戶。也就是說session級緩存只能給一個線程用,別的線程用不了,一級緩存就是和線程綁定的。其生命周期和session的生命周期一致,當前session一旦關閉,一級緩存就會消失,因此,一級緩存也叫session緩存或者事務級緩存,一級緩存只存儲實體對象,不會緩存一般的對象屬性,即:當獲得對象后,就將該對象緩存起來,如果在同一個session中再去獲取這個對象時,它會先判斷緩存中有沒有這個對象的ID,如果有,就直接從緩存中取出,否則,則去訪問數據庫,取了以后同時會將這個對象緩存起來。
Session內置不能被卸載,Session的緩存是事務范圍的緩存(Session對象的生命周期通常對應一個數據庫事務或者一個應用事務)。一級緩存中,持久化類的每個實例都具有唯一的OID。
緩存和連接池的區別:緩存和池都是放在內存里,實現是一樣的,都是為了提高性能的。但有細微的差別,池是重量級的,里面的數據是一樣的,比如一個里放100個connection連接對象,這100個對象都是一樣的。而緩存里的數據,每個都不一樣。比如讀取100條數據庫記錄放到緩存里,這100條記錄都不一樣。
以下我們結合具體示例來學習一級緩存:
import java.io.Serializable; import java.util.Iterator; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration;public class CacheDemo {private static SessionFactory sessionFactory;static{sessionFactory = new Configuration().configure().buildSessionFactory();}/*** 同一個Session發出兩次load方法查詢* * 第二次查詢的數據和第一次相同,第二次load方法是從緩存里取數據,而不會再發出sql語句到數據庫中進行查詢。*/public void cacheTest1(){Session session = sessionFactory.openSession();session.beginTransaction();//第一次load查詢Student student = session.load(Student.class, 1);System.out.println("student.name : " + student.getName());System.out.println("*************************************");//第二次load查詢(使用緩存)student = session.load(Student.class, 1);System.out.println("student.name : " + student.getName());}/*** 同一個Session發出兩次get方法查詢* * 第二次查詢的數據和第一次相同,第二次get方法是從緩存里取數據,而不會再發出sql語句到數據庫中進行查詢。*/public void cacheTest2(){Session session = sessionFactory.openSession();session.beginTransaction();//第一次get查詢Student student = session.get(Student.class, 1);System.out.println("student.name : " + student.getName());System.out.println("*************************************");//第二次get查詢(使用緩存)student = session.get(Student.class, 1);System.out.println("student.name : " + student.getName());}/*** 同一個Session發出兩次iterator查詢對象* * 說起iterator查詢,我們會想到,iterator查詢在沒有緩存的情況下會有N+1的問題。* 執行上面的代碼,我們可以看到,第一次iterator查詢會發出N+1條語句,第一條sql語句查詢所有的ID,* 然后根據ID查詢實體對象,有N個ID就出N條語句查詢實體。第二次iterator查詢,卻只發一條sql語句,* 查詢所有的ID,然后根據ID到緩存里取實體對象,不再發sql語句到數據庫里查詢了。*/public void cacheTest3(){Session session = sessionFactory.openSession();session.beginTransaction();//第一次iterator查詢Iterator iter = session.createQuery("from student s where s.id < 5").iterate();while (iter.hasNext()) {Student student = (Student)iter.next();System.out.println("student.name : " + student.getName()); }System.out.println("*************************************");//第二次iterator查詢(使用緩存)iter = session.createQuery("from student s where s.id < 5").iterate();while (iter.hasNext()) {Student student = (Student)iter.next();System.out.println("student.name : " + student.getName()); }}/*** 同一個Session發出兩次iterator查詢對象的普通屬性* * 執行上面的代碼,我們可以看到,第一次iterator查詢會發出N+1條語句,第二次iterator查詢還是發了N+1條sql語句,* 因為一級緩存只是緩存實體對象,而不緩存對象屬性,所以在iterate第二次查詢普通屬性時,無法從緩存中獲取,* 從而跟第一次查詢發出相同條數的sql語句。*/public void cacheTest4(){Session session = sessionFactory.openSession();session.beginTransaction();//第一次iterator查詢Iterator iter = session.createQuery("select s.name from student s where s.id < 5").iterate();while (iter.hasNext()) {String name = (String)iter.next();System.out.println("student.name : " + name); }System.out.println("*************************************");//第二次iterator查詢(使用緩存)iter = session.createQuery("select s.name from student s where s.id < 5").iterate();while (iter.hasNext()) {String name = (String)iter.next();System.out.println("student.name : " + name); }}/*** 兩個session,每個session發出一個load方法查詢實體對象* * 第一個session的load方法會發出sql語句查詢實體對象,第二個session的load方法也會發出sql語句查詢實體對象。* session間不能共享一級緩存的數據,第一個session中的數據在其被關閉的時候就已經不存在了,* 所以第二個session的load方法查詢相同的數據還是要到數據庫中查詢。*/public void cacheTest5(){Session session = null;//第一個session的load查詢try {session = sessionFactory.openSession();session.beginTransaction();Student student = session.load(Student.class, 1);System.out.println("student.name : " + student.getName());session.getTransaction().commit();} catch (Exception e) {session.getTransaction().rollback();}finally{session.close();//關閉session}System.out.println("***********************************");//第二個session的load查詢try {session = sessionFactory.openSession();session.beginTransaction();Student student = session.load(Student.class, 1);System.out.println("student.class : " + student.getName());session.getTransaction().commit();} catch (Exception e) {session.getTransaction().rollback();}finally{session.close();}}/*** 同一個session,先調用save方法再調用load方法查詢剛剛save的數據* * 先save保存實體對象,再用load方法查詢剛剛save的實體對象,則load方法不會發出sql語句到數據庫查詢的,* 而是到緩存里取數據,因為save方法也支持緩存。當然前提是同一個session。*/public void cacheTest6(){Session session = sessionFactory.openSession();session.beginTransaction();Student student = new Student().setName("Tom");//save方法返回實體對象的IDSerializable id = session.save(student);//load查詢剛剛save的數據student = session.load(Student.class, 1);System.out.println("student.name : " + student.getName());}/*** 大批量的數據添加* * 大批量數據添加時,會造成內存溢出的,因為save方法支持緩存,每save一個對象就往緩存里放,如果對象足夠多內存肯定要溢出。* 一般的做法是先判斷一下save了多少個對象,如果save了40個對象就對緩存手動的清理緩存,這樣就不會造成內存溢出。* * 注意:清理緩存前,要手動調用flush方法同步到數據庫,否則save的對象就沒有保存到數據庫里。* * 建議:大批量數據的添加還是不要使用hibernate,這是hibernate弱項。* 可以使用jdbc(速度也不會太快,只是比hibernate好一點),或者使用工具產品來實現,* 比如oracle的Oracle SQL Loader,導入數據特別快。*/public void cacheTest7(){Session session = sessionFactory.openSession();session.beginTransaction();for (int i = 0; i < 100; i++) {Student student = new Student().setName("Tom"+i);session.save(student);//每40條數據更新一次if (i % 40 == 0) {session.flush();session.clear();//清除緩存數據}}}}二級緩存
二級緩存也稱為進程緩存或者sessionFactory級的緩存,它可以被所有的session共享,二級緩存的生命周期和sessionFactory的生命周期一致,二級緩存也是只存儲實體對象。
二級緩存的一般過程如下:
①:條件查詢的時候,獲取查詢到的實體對象
②:把獲得到的所有數據對象根據ID放到二級緩存中
③:當Hibernate根據ID訪問數據對象時,首先從sesison的一級緩存中查,查不到的時候如果配置了二級緩存,會從二級緩存中查找,如果還查不到,再查詢數據庫,把結果按照ID放入到緩存中
④:進行delete、update、add操作時會同時更新緩存
由于SessionFactory對象的生命周期和應用程序的整個過程對應,因此Hibernate二級緩存是進程范圍或者集群范圍的緩存,有可能出現并發問題,因此需要采用適當的并發訪問策略,該策略為被緩存的數據提供了事務隔離級別。
第二級緩存是可選的,是一個可配置的插件,默認下SessionFactory不會啟用這個插件。Hibernate提供了org.hibernate.cache.CacheProvider接口,它充當緩存插件與Hibernate之間的適配器。 二級緩存比較復雜,hibernate做了一些優化,和一些第三方的緩存產品做了集成。。hibernate提供了一個簡單實現,用Hashtable做的,只能作為我們的測試使用,商用還是需要第三方產品。
使用緩存,肯定是長時間不改變的數據,如果經常變化的數據放到緩存里就沒有太大意義了。因為經常變化,還是需要經常到數據庫里查詢,那就沒有必要用緩存了。
什么樣的數據適合存放到第二級緩存中?
1) 很少被修改的數據
2) 不是很重要的數據,允許出現偶爾并發的數據
3) 不會被并發訪問的數據
4) 常量數據
什么樣的數據不適合存放到第二級緩存中?
1) 經常被修改的數據
2) 絕對不允許出現并發訪問的數據,如財務數據,絕對不允許出現并發
3) 與其他應用共享的數據。
我們以一個和EHCache二級緩存產品集成的示例來看一下程序代碼實現:EHCache的jar文件在hibernate的lib里,我們還需要設置一系列的緩存使用策略,需要一個配置文件ehcache.xml來配置。這個文件放在類路徑下。
//默認配置,所有的類都遵循這個配置 <defaultCache//緩存里可以放10000個對象maxElementsInMemory="10000"//過不過期,如果是true就是永遠不過期eternal="false"//一個對象被訪問后多長時間還沒有訪問就失效(120秒還沒有再次訪問就失效)timeToIdleSeconds="120"//對象存活時間(120秒),如果設置永不過期,這個就沒有必要設了timeToLiveSeconds="120"//溢出的問題,如果設成true,緩存里超過10000個對象就保存到磁盤里overflowToDisk="true" />我們也可以對某個對象單獨配置: <cache name="com.bjpowernode.hibernate.Student"maxElementsInMemory="100"eternal="false"timeToIdleSeconds="10000"timeToLiveSeconds="10000"overflowToDisk="true" /> <!-- 配置緩存提供商 --> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property> <!-- 啟用二級緩存,這也是它的默認配置 --> <property name="hibernate.cache.use_second_level_cache">true</property>啟用二級緩存的配置可以不寫的,因為默認就是true開啟二級緩存。必須還手動指定那些實體類的對象放到緩存里在hibernate.cfg.xml里:
//在<sessionfactory>標簽里,在<mapping>標簽后配置<class-cache class="com.bjpowernode.hibernate.Student" usage="read-only"/>或者在實體類映射文件里:
//在<class>標簽里,<id>標簽前配置 <cache usage="read-only"/>usage屬性表示使用緩存的策略,一般優先使用read-only,表示如果這個數據放到緩存里了,則不允許修改,如果修改就會報錯。這就要注意我們放入緩存的數據不允許修改。因為放緩存里的數據經常修改,也就沒有必要放到緩存里。
使用read-only策略效率好,因為不能改緩存。但是可能會出現臟數據的問題,這個問題解決方法只能依賴緩存的超時,比如上面我們設置了超時為120秒,120后就可以對緩存里對象進行修改,而在120秒之內訪問這個對象可能會查詢臟數據的問題,因為我們修改對象后數據庫里改變了,而緩存卻不能改變,這樣造成數據不同步,也就是臟數據的問題。
第二種緩存策略read-write,當持久對象發生變化,緩存里就會跟著變化,數據庫中也改變了。這種方式需要加解鎖,效率要比第一種慢。
下面我們結合具體示例來學習二級緩存:
/*** 開啟二級緩存,兩個session,每個session發出一個load/get方法查詢實體對象** 第二次查詢的數據和第一次相同,不會發出查詢語句,因為配置二級緩存,session可以共享二級緩存中的數據。* 所以第二次load方法是從緩存里取數據,而不會再發出sql語句到數據庫中進行查詢。*/public void cacheTest1(){Session session = null;//第一次load查詢try {session = sessionFactory.openSession();session.beginTransaction();Student student = session.load(Student.class, 1);System.out.println("student.name : " + student.getName());session.getTransaction().commit();} catch (Exception e) {session.getTransaction().rollback();}finally{session.close();//關閉session}System.out.println("***********************************");//第二次load查詢try {session = sessionFactory.openSession();session.beginTransaction();Student student = session.load(Student.class, 1);System.out.println("student.class : " + student.getName());session.getTransaction().commit();} catch (Exception e) {session.getTransaction().rollback();}finally{session.close();}}}注意:二級緩存必須讓sessionfactory管理,讓sessionfactory來清除二級緩存。
sessionFactory.evict(Student.class);//清除二級緩存中所有student對象 sessionFactory.evict(Student.class,1);//清除二級緩存中id為1的student對象如果在第一個session調用load或get方法查詢數據后,把二級緩存清除了,那么第二個session調用load或get方法查詢相同的數據時,還是會發出sql語句查詢數據庫的,因為緩存里沒有數據只能到數據庫里查詢。
我們查詢數據后會默認自動的放到二級和一級緩存里,如果我們想查詢的數據不放到緩存里,也是可以的。也就是說我們可以控制一級緩存和二級緩存的交換。
session.setCacheMode(CacheMode.IGNORE);禁止將一級緩存中的數據往二級緩存里放。還是用上面代碼測試,在第一個session調用load方法前,執行session.setCacheMode(CacheMode.IGNORE);這樣load方法查詢的數據不會放到二級緩存里。那么第二個session執行load方法查詢相同的數據,會發出sql語句到數據庫中查詢,因為二級緩存里沒有數據,一級緩存因為不同的session不能共享,所以只能到數據庫里查詢。
上面我們講過大批量的數據添加時可能會出現溢出,解決辦法是每當天就40個對象后就清理一次一級緩存。如果我們使用了二級緩存,光清理一級緩存是不夠的,還要禁止一二級緩存交互,在save方法前調用
session.setCacheMode(CacheMode.IGNORE)
二級緩存也不會存放普通屬性的查詢數據,這和一級緩存是一樣的,只存放實體對象。session級的緩存對性能的提高沒有太大的意義,因為生命周期太短了。
查詢緩存
查詢緩存意義不大,查詢緩存說白了就是存放由list方法或iterate方法查詢的數據。我們在查詢時很少出現完全相同條件的查詢,這也就是命中率低,這樣緩存里的數據總是變化的,所以說意義不大。除非是多次查詢都是查詢相同條件的數據,也就是說返回的結果總是一樣,這樣配置查詢緩存才有意義。我們這里不做過多解釋。
Hibernate查找對象如何應用緩存?
當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那么從二級緩存中查;如果都查不到,再查詢數據庫,把結果按照ID放入到緩存刪除、更新、增加數據的時候,同時更新緩存。
最后貼出一個一級緩存和二級緩存的對比圖:
總結
以上是生活随笔為你收集整理的【大话Hibernate】hibernate缓存详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【大话Hibernate】Hiberna
- 下一篇: 【大话hibernate】hiberna