Hibernate之緩存詳解

Hibernate中提供了兩級緩存,一級緩存是Session級別的緩存,它屬於事務範圍的緩存,該級緩存由hibernate管理,應用程序無需干預;二級緩存是SessionFactory級別的緩存,該級緩存可以進行配置和更改,並且可以動態加載和卸載,hibernate還爲查詢結果提供了一個查詢緩存,它依賴於二級緩存;

一,緩存的概念

緩存是位於應用程序和永久性數據存儲源之間用於臨時存放複製數據的內存區域,緩存可以降低應用程序之間讀寫永久性數據存儲源的次數,從而提高應用程序的運行性能;

hibernate在查詢數據時,首先會到緩存中查找,如果找到就直接使用,找不到時才從永久性數據存儲源中檢索,因此,把頻繁使用的數據加載到緩存中,可以減少應用程序對永久性數據存儲源的訪問,使應用程序的運行性能得以提升;

二,緩存的範圍

緩存範圍決定了緩存的生命週期,緩存範圍分爲3類:

1>事務範圍

緩存只能被當前事務訪問,緩存的生命週期依賴於事務的生命週期,事務結束時,緩存的生命週期也結束了;

2>進程範圍

緩存被進程內的所有事務共享,這些事務會併發訪問緩存,需要對緩存採用必要的事務隔離機制,緩存的生命週期取決與進程的生命週期,進程結束,緩存的生命週期也結束了;

3>集羣範圍

緩存被一個或多個計算機的進程共享,緩存中的數據被複制到集羣中的每個進行節點,進程間通過遠程通信來保證緩存中數據的一致性;

在查詢時,如果在事務範圍內的緩存中沒有找到,可以到進程範圍或集羣範圍的緩存中查找,如果還沒找到,則到數據庫中查詢;

三,Hibernate中的第一級緩存

Hibernate的一級緩存由Session提供,只存在於Session的生命週期中,當應用程序調用Session接口的save(),update(),saveOrupDate(),get(),load()或者Query和Criteria實例的list(),iterate()等方法時,如果Session緩存中沒有相應的對象,hibernate就會把對象加入到一級緩存中,當session關閉時,該Session所管理的一級緩存也會立即被清除;

1,get查詢測試:

1>在同一個session中發出兩次get查詢

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public void Query(){  
  2.         Session sess = HibernateSessionFactory.getSession();  
  3.         Transaction tx = sess.beginTransaction();  
  4.         Student s1 = (Student)sess.get(Student.class2);  
  5.         System.out.println(s1.getName());  
  6.         Student s2 = (Student)sess.get(Student.class2);  
  7.         System.out.println(s2.getName());  
  8.         tx.commit();  
  9.         HibernateSessionFactory.closeSession();  
  10.     }</strong></span>  
上面的兩次查詢,第一次執行了get方法查詢了數據庫,產生了一條sql語句,第二次執行get方法時,由於在一級緩存中找到了該對象,因此不會查詢數據庫,不再發出sql語句;

2>開啓兩個session中發出兩次get查詢

[java] view plain copy
  1. <span style="font-size:18px;"><strong>        public void Query(){  
  2.         Session sess1 = HibernateSessionFactory.getSession();  
  3.         Transaction tx1 = sess1.beginTransaction();  
  4.         Student s1 = (Student)sess1.get(Student.class2);  
  5.         System.out.println(s1.getName());  
  6.         tx1.commit();  
  7.         HibernateSessionFactory.closeSession();  
  8.         Session sess2 = HibernateSessionFactory.getSession();  
  9.         Transaction tx2 = sess2.beginTransaction();  
  10.         Student s2 = (Student)sess2.get(Student.class2);  
  11.         System.out.println(s2.getName());  
  12.         tx2.commit();  
  13.         HibernateSessionFactory.closeSession();  
  14.     }</strong></span>  
上面的兩次查詢,兩次執行get方法時都查詢了數據庫,產生了兩條sql語句,原因在於,第一次執行get方法查詢出結果後,關閉了session,緩存被清除了,第二次執行get方法時,從緩存中找不到結果,只能到數據庫查詢;

2,iterate查詢測試

插入一個iteritor查詢方式:

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public void Query(){  
  2.         Session sess = HibernateSessionFactory.getSession();  
  3.         Transaction tx = sess.beginTransaction();  
  4.         Query query = sess.createQuery("from Student");  
  5.         Iterator iter = query.iterate();  
  6.         while(iter.hasNext()){  
  7.                System.out.println(((Student)iter.next()).getName());  
  8.         }  
  9.         tx.commit();  
  10.         HibernateSessionFactory.closeSession();  
  11.     }</strong></span>  
[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public void Query(){  
  2.         Session sess = HibernateSessionFactory.getSession();  
  3.         Transaction tx = sess.beginTransaction();  
  4.         Student s1 = (Student)sess.createQuery("from Student s where s.id = 2").iterate().next();  
  5.         System.out.println(s1.getName());  
  6.         Student s2 = (Student)sess.createQuery("from Student s where s.id = 2").iterate().next();  
  7.         System.out.println(s2.getName());  
  8.         tx.commit();  
  9.         HibernateSessionFactory.closeSession();  
  10.     }</strong></span>  
上面的代碼執行後會生成三條sql語句,第一次執行iterate().next()時會發出查詢id的sql語句(第一條sql語句),得到s1對象,使用s1對象獲得name屬性值時會發出相應的查詢實體對象的sql語句(第二條sql語句),第二次執行iterate().next()時會發出查詢id的sql語句(第三條sql語句),但是不會發出查詢實體對象的sql語句,因爲hibernate使用緩存,不會發出sql語句

3,iterate查詢屬性測試:

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public void Query(){  
  2.         Session sess = HibernateSessionFactory.getSession();  
  3.         Transaction tx = sess.beginTransaction();  
  4.         String name1 = (String)sess.createQuery("select s.name from Student s where s.id = 2").iterate().next();  
  5.         System.out.println(name1);  
  6.         String name2 = (String)sess.createQuery("select s.name from Student s where s.id = 2").iterate().next();  
  7.         System.out.println(name2);  
  8.         tx.commit();  
  9.         HibernateSessionFactory.closeSession();  
  10.     }</strong></span>  
上面的代碼第一次執行iterate().next()時發出查詢屬性的sql語句,第二次執行iterate().next()時也會發出查詢屬性的sql語句,這是因爲iterate查詢普通屬性,一級緩存不會緩存,所以會發出sql;

4,在一個session中先save,再執行load查詢

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public void Query(){  
  2.         Session sess = HibernateSessionFactory.getSession();  
  3.         Transaction tx = sess.beginTransaction();  
  4.         Student s = new Student(8"newAcc"88);  
  5.         Serializable id = sess.save(s);  
  6.         tx.commit();  
  7.         Student s1 = (Student)sess.load(Student.class8);  
  8.         System.out.println(s1.getName());  
  9.         HibernateSessionFactory.closeSession();  
  10.     }</strong></span>  
上面的代碼執行save操作時,它會在緩存裏放一份,執行load操作時,不會發出sql語句,因爲save使用了緩存;

--------------------

Session接口爲應用程序提供了兩個管理緩存的方法:

1>evict()方法:用於將某個對象從Session的一級緩存中清除;

2>clear()方法:用於將一級緩存中的所有對象全部清楚;

測試:

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public void Query(){  
  2.         Session sess = HibernateSessionFactory.getSession();  
  3.         Transaction tx = sess.beginTransaction();  
  4.         Student s = (Student)sess.load(Student.class1);  
  5.         System.out.println(s.getName());  
  6.         sess.clear();//清除一級緩存中的所有對象  
  7.         Student s1 = (Student)sess.load(Student.class1);  
  8.         System.out.println(s1.getName());  
  9.         tx.commit();  
  10.         HibernateSessionFactory.closeSession();  
  11.     }</strong></span>  
從上面的代碼可以看出,clear方法可以管理一級緩存,一級緩存無法取消,但是可以管理,第一次執行load操作時會發出sql語句,接着由於一級緩存中的實體被清除了,因此第二次執行load操作時也會發出sql語句;

四,Hibernate中的第二級緩存

二級緩存是一個可插拔的緩存插件,它是由SessionFactory負責管理的;

由於SessionFactory對象的生命週期與應用程序的整個過程對應,通常一個應用程序對應一個SessionFactory,因此,二級緩存是進程範圍或者集羣範圍的緩存;

與一級緩存一樣,二級緩存也是根據對象的id來加載與緩存,當執行某個查詢獲得結果集爲實體對象集時,hibernate就會把它們按照對象id加載到二級緩存中,在訪問指定的id的對象時,首先從一級緩存中查找,找到就直接使用,找不到則轉到二級緩存中查找(必須配置且啓用二級緩存),如果二級緩存中找到,則直接使用,否則會查詢數據庫,並將查詢結果根據對象的id放到緩存中;

1,常用的二級緩存插件

Hibernate的二級緩存功能是通過配置二級緩存插件來實現的,常用的二級緩存插件包括EHCache,OSCache,SwarmCache和JBossCache。其中EHCache緩存插件是理想的進程範圍的緩存實現,此處以使用EHCache緩存插件爲例,來介紹如何使用hibernate的二級緩存;

2,Hibernate中使用EHCache的配置

1>引入EHCache相關的jar包;

lib\optional\ehcache下的三個jar包;

2>創建EHCache的配置文件ehcache.xml

[html] view plain copy
  1. <span style="font-size:18px;"><strong><ehcache>  
  2.     <diskStore path="java.io.tmpdir"/>  
  3.     <defaultCache  
  4.         maxElementsInMemory="10000"  
  5.         eternal="false"  
  6.         timeToIdleSeconds="120"  
  7.         timeToLiveSeconds="120"  
  8.         overflowToDisk="true"  
  9.         />  
  10.     <cache name="sampleCache1"  
  11.         maxElementsInMemory="10000"  
  12.         eternal="false"  
  13.         timeToIdleSeconds="300"  
  14.         timeToLiveSeconds="600"  
  15.         overflowToDisk="true"  
  16.         />  
  17.     <cache name="sampleCache2"  
  18.         maxElementsInMemory="1000"  
  19.         eternal="true"  
  20.         timeToIdleSeconds="0"  
  21.         timeToLiveSeconds="0"  
  22.         overflowToDisk="false"  
  23.         />  
  24. </ehcache></strong></span>  
在上述配置中,diskStore元素設置緩存數據文件的存儲目錄;defaultCache元素設置緩存的默認數據過期策略;cache元素設置具體的命名緩存的數據過期策略。每個命名緩存代表一個緩存區域,命名緩存機制允許用戶在每個類以及類的每個集合的粒度上設置數據過期策略;
在defaultCache元素中,maxElementsInMemory屬性設置緩存對象的最大數目;eternal屬性指定是否永不過期,true爲不過期,false爲過期;timeToldleSeconds屬性設置對象處於空閒狀態的最大秒數;timeToLiveSeconds屬性設置對象處於緩存狀態的最大秒數;overflowToDisk屬性設置內存溢出時是否將溢出對象寫入硬盤;

3>在Hibernate配置文件裏面啓用EHCache

在hibernate.cfg.xml配置文件中,啓用EHCache的配置如下:

[html] view plain copy
  1. <span style="font-size:18px;"><strong>                <!-- 啓用二級緩存 -->  
  2.         <property name="hibernate.cache.use_second_level_cache">true</property>  
  3.         <!-- 設置二級緩存插件EHCache的Provider類 -->  
  4.         <property name="hibernate.cache.region.factory_class">  
  5.         org.hibernate.cache.ehcache.EhCacheRegionFactory</property></strong></span>  
4>配置哪些實體類的對象需要二級緩存,有兩種方式:

1>>在實體類的映射文件裏面配置

在需要進行緩存的持久化對象的映射文件中配置相應的二級緩存策略,如User,hbm.xml:

[html] view plain copy
  1. <span style="font-size:18px;"><strong><?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping>  
  6.     <class name="com.hibtest1.entity.User" table="user" catalog="bookshop">  
  7.         <cache usage="read-write"/>  
  8.         <id name="id" type="java.lang.Integer">  
  9.             <column name="Id" />  
  10.             <generator class="native" />  
  11.         </id>  
  12.         <property name="loginName" type="java.lang.String">  
  13.             <column name="LoginName" length="50" />  
  14.         </property>  
  15.           
  16.     </class>  
  17. </hibernate-mapping></strong></span>  
映射文件中使用<cache>元素設置持久化類User的二級緩存併發訪問策略,usage屬性取值爲read-only時表示只讀型併發訪問策略;read-write表示讀寫型併發訪問策略;nonstrict-read-write表示非嚴格讀寫型併發訪問策略;EHCache插件不支持transactional(事務型併發訪問策略)。
注意:<cache>元素只能放在<class>元素的內部,而且必須處在<id>元素的前面,<cache>元素放在哪些<class>元素下面,就說明會對哪些類進行緩存;

2>>在hibernate配置文件中統一配置,強烈推薦使用這種方式:

在hibernate.cfg.xml文件中使用<class-cache>元素來配置哪些實體類的對象需要二級緩存:

[java] view plain copy
  1. <span style="font-size:18px;"><strong><span style="font-size:18px;"><strong><span style="font-size:18px;"><strong><class-cache usage="read-only" class="com.anlw.entity.Student"/></strong></span></strong></span></strong></span>  
在<class-cache>元素中,usage屬性指定緩存策略,需要注意<class-cache>元素必須放在所有<mapping>元素的後面;

3,Hibernate中使用EHCache的測試:

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public void Query(){  
  2.         Session sess1 = HibernateSessionFactory.getSession();  
  3.         Student s1 = (Student)sess1.get(Student.class1);  
  4.         System.out.println(s1.getName());  
  5.         HibernateSessionFactory.closeSession();  
  6.         Session sess2 = HibernateSessionFactory.getSession();  
  7.         Student s2 = (Student)sess2.get(Student.class1);  
  8.         System.out.println(s2.getName());  
  9.         HibernateSessionFactory.closeSession();  
  10.     }</strong></span>  
上面的代碼,第一次執行get方法查詢出結果後,關閉了session,一級緩存被清除了,由於配置並啓用了二級緩存,查詢出的結果會放入二級緩存,第二次執行get方法時,首先從一級緩存中查找,沒有找到,然後轉到二級緩存查找,二級緩存中找到結果,就不需要從數據庫查詢了。

注意:在hibernate配置二級緩存時屬性的順序如下,順序錯了會空指針異常:

[java] view plain copy
  1. <span style="font-size:18px;"><strong>                <!-- 啓用二級緩存 -->  
  2.         <property name="hibernate.cache.use_second_level_cache">true</property>  
  3.         <!-- 設置二級緩存插件EHCache的Provider類 -->  
  4.         <property name="hibernate.cache.region.factory_class">  
  5.         org.hibernate.cache.ehcache.EhCacheRegionFactory</property>  
  6.         <mapping class="com.anlw.entity.Student"/>  
  7.         <class-cache usage="read-only" class="com.anlw.entity.Student"/></strong></span>  
先緩存配置,再mapping,最後calss-cache;

五,Hibernate中的查詢緩存

對於經常使用的查詢語句,如果啓用了查詢緩存 ,當第一次執行查詢語句時,hibernate會將查詢結果存儲在二級緩存中,以後再次執行該查詢語句時,從緩存中獲取查詢結果,從而提高查詢性能;

hibernate的查詢緩存主要是針對普通屬性結果集的緩存,而對於實體對象的結果集只緩存id;

查詢緩存的生命週期,若當前關聯的表發生修改,那麼查詢緩存的生命週期結束;

1,查詢緩存的配置

查詢緩存基於二級緩存,使用查詢緩存前,必須首先配置好二級緩存;

在配置了二級緩存的基礎上,在hibernate的配置文件hibernate.cfg.xml中添加如下配置,可以啓用查詢緩存:

<property name="hibernate.cache.use_query_cache">false</property>

此外在程序中還必須手動啓用查詢緩存:

query.setCacheable(true);

2,測試查詢緩存:

1>開啓查詢緩存,關閉二級緩存,開啓一個session,分別調用query.list查詢屬性,測試前,先在先在hibernate.cfg.xml文件中開啓查詢緩存,關閉二級緩存,如下所示:

<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.use_second_level_cache">false</property>

[java] view plain copy
  1. <span style="font-size:18px;"><strong>    public static void query(){  
  2.         Session sess = HibernateSessionFactory.getSession();  
  3.         Transaction tx = sess.beginTransaction();  
  4.         Query query = sess.createQuery("select s.name from Student s");  
  5.         query.setCacheable(true);  
  6.         List names = query.list();  
  7.         for(Iterator iter = names.iterator();iter.hasNext();){  
  8.             String name = (String)iter.next();  
  9.             System.out.println(name);  
  10.         }  
  11.         System.out.println("----------");  
  12.         query = sess.createQuery("select s.name from Student s");  
  13.         query.setCacheable(true);  
  14.         names = query.list();  
  15.         for(Iterator iter = names.iterator();iter.hasNext();){  
  16.             String name = (String)iter.next();  
  17.             System.out.println(name);  
  18.         }  
  19.         tx.commit();  
  20.         HibernateSessionFactory.closeSession();  
  21.     }  
  22. </strong></span>  

第二次沒有去查數據庫,因爲啓用了查詢緩存;

2>開啓查詢緩存,關閉二級緩存,開啓兩個session,分別調用query.list查詢屬性,測試前,先在先在hibernate.cfg.xml文件中開啓查詢緩存,關閉二級緩存,如下所示:

<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.use_second_level_cache">false</property>

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public static void query(){  
  2.         Session sess1 = HibernateSessionFactory.getSession();  
  3.         Transaction tx1 = sess1.beginTransaction();  
  4.         Query query = sess1.createQuery("select s.name from Student s");  
  5.         query.setCacheable(true);  
  6.         List names = query.list();  
  7.         for(Iterator iter = names.iterator();iter.hasNext();){  
  8.             String name = (String)iter.next();  
  9.             System.out.println(name);  
  10.         }  
  11.         tx1.commit();  
  12.         HibernateSessionFactory.closeSession();  
  13.         System.out.println("----------");  
  14.         Session sess2 = HibernateSessionFactory.getSession();  
  15.         Transaction tx2 = sess2.beginTransaction();  
  16.         query = sess2.createQuery("select s.name from Student s");  
  17.         query.setCacheable(true);  
  18.         names = query.list();  
  19.         for(Iterator iter = names.iterator();iter.hasNext();){  
  20.             String name = (String)iter.next();  
  21.             System.out.println(name);  
  22.         }  
  23.         tx2.commit();  
  24.         HibernateSessionFactory.closeSession();  
  25.     }</strong></span>  
第二次沒有去查數據庫,因爲查詢緩存生命週期與session生命週期無關;

3>開啓查詢緩存,關閉二級緩存,開啓兩個session,分別調用query.list查詢實體對象,測試前,先在先在hibernate.cfg.xml文件中開啓查詢緩存,關閉二級緩存,如下所示:

<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.use_second_level_cache">false</property>

[java] view plain copy
  1. <span style="font-size:18px;"><strong>  public static void query(){  
  2.         Session sess1 = HibernateSessionFactory.getSession();  
  3.         Transaction tx1 = sess1.beginTransaction();  
  4.         Query query = sess1.createQuery("from Student");  
  5.         query.setCacheable(true);  
  6.         List student = query.list();  
  7.         for(Iterator iter = student.iterator();iter.hasNext();){  
  8.             Student s = (Student)iter.next();  
  9.             System.out.println(s.getName()+"--"+s.getAge());  
  10.         }  
  11.         tx1.commit();  
  12.         HibernateSessionFactory.closeSession();  
  13.         System.out.println("----------");  
  14.         Session sess2 = HibernateSessionFactory.getSession();  
  15.         Transaction tx2 = sess2.beginTransaction();  
  16.         query = sess2.createQuery("from Student");  
  17.         query.setCacheable(true);  
  18.         student = query.list();  
  19.         for(Iterator iter = student.iterator();iter.hasNext();){  
  20.             Student s = (Student)iter.next();  
  21.             System.out.println(s.getName()+"--"+s.getAge());  
  22.         }  
  23.         tx2.commit();  
  24.         HibernateSessionFactory.closeSession();  
  25.     }</strong></span>  
查詢結果如下:
[java] view plain copy
  1. <span style="font-size:18px;"><strong>Hibernate:   
  2.     select  
  3.         student0_.id as id1_0_,  
  4.         student0_.age as age2_0_,  
  5.         student0_.name as name3_0_   
  6.     from  
  7.         student student0_  
  8. anliwenaaa--1  
  9. test--2  
  10. ----------  
  11. Hibernate:   
  12.     select  
  13.         student0_.id as id1_0_0_,  
  14.         student0_.age as age2_0_0_,  
  15.         student0_.name as name3_0_0_   
  16.     from  
  17.         student student0_   
  18.     where  
  19.         student0_.id=?  
  20. Hibernate:   
  21.     select  
  22.         student0_.id as id1_0_0_,  
  23.         student0_.age as age2_0_0_,  
  24.         student0_.name as name3_0_0_   
  25.     from  
  26.         student student0_   
  27.     where  
  28.         student0_.id=?  
  29. anliwenaaa--1  
  30. test--2</strong></span>  
第二次查詢數據庫時,會發出n條sql語句,因爲開啓了查詢緩存,關閉了二級緩存,那麼查詢緩存會緩存實體對象的id,所以hibernate會根據實體對象的id去查詢相應的實體,如果緩存中不存在相應的實體,那麼將發出根據實體id查詢的sql語句,否則不會發出sql,使用緩存中的數據;

4>開啓查詢緩存,開啓二級緩存,開啓兩個session,分別調用query.list查詢實體對象,測試前,先在先在hibernate.cfg.xml文件中開啓查詢緩存,開啓二級緩存,如下所示:

<property name="hibernate.cache.use_query_cache">true</property>
<property name="hibernate.cache.use_second_level_cache">true</property>

代碼和3>一樣,但是結果不同,第二次不會發出sql,因爲開啓了二級緩存和緩存查詢,查詢緩存緩存了實體對象的id,hibernate會根據實體對象的id到二級緩存中取得相應的數據;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章