Hibernate 一級緩存和二級緩存


轉載自:http://www.cnblogs.com/200911/archive/2012/10/09/2716873.html


1、 什麼是緩存

 

緩存是介於物理數據源與應用程序之間,是對數據庫中的數據複製一份臨時放在內存中的容器,其作用是爲了減少應用程序對物理數據源訪問的次數,從而提高了應用程序的運行性能。Hibernate在進行讀取數據的時候,根據緩存機制在相應的緩存中查詢,如果在緩存中找到了需要的數據(我們把這稱做“緩存命中"),則就直接把命中的數據作爲結果加以利用,避免了大量發送SQL語句到數據庫查詢的性能損耗。

 

Hibernate緩存分類:

 

一、Session緩存(又稱作事務緩存):Hibernate內置的,不能卸除。

 

緩存範圍:緩存只能被當前Session對象訪問。緩存的生命週期依賴於Session的生命週期,當Session被關閉後,緩存也就結束生命週期。

 

二、SessionFactory緩存(又稱作應用緩存):使用第三方插件,可插拔。

 

緩存範圍:緩存被應用範圍內的所有session共享,不同的Session可以共享。這些session有可能是併發訪問緩存,因此必須對緩存進行更新。緩存的生命週期依賴於應用的生命週期,應用結束時,緩存也就結束了生命週期,二級緩存存在於應用程序範圍。

 

1.1、       一級緩存

 

Hibernate一些與一級緩存相關的操作(時間點):

 

數據放入緩存:

 

1. save()。當session對象調用save()方法保存一個對象後,該對象會被放入到session的緩存中。

 

2. get()load()。當session對象調用get()或load()方法從數據庫取出一個對象後,該對象也會被放入到session的緩存中。

 

3. 使用HQL和QBC等從數據庫中查詢數據。

 

例如:數據庫有一張表叫Customer,有字段id,username等。

 

 

public class Client
{
   public static void main(String[] args)
    {
       Session session = HibernateUtil.getSessionFactory().openSession();
       Transaction tx = null;
       try
       {
           /*開啓一個事務*/
           tx = session.beginTransaction();
           /*從數據庫中獲取id="402881e534fa5a440134fa5a45340002"的Customer對象*/
           Customer customer1 = (Customer)session.get(Customer.class,"402881e534fa5a440134fa5a45340002");
           System.out.println("customer.getUsernameis"+customer1.getUsername());
            /*事務提交*/
           tx.commit();
          
           System.out.println("-------------------------------------");
          
           /*開啓一個新事務*/
           tx = session.beginTransaction();
           /*從數據庫中獲取id="402881e534fa5a440134fa5a45340002"的Customer對象*/
           Customer customer2 = (Customer)session.get(Customer.class,"402881e534fa5a440134fa5a45340002");
           System.out.println("customer2.getUsernameis"+customer2.getUsername());
           /*事務提交*/
           tx.commit();
          
           System.out.println("-------------------------------------");
          
           /*比較兩個get()方法獲取的對象是否是同一個對象*/
           System.out.println("customer1 == customer2 result is"+(customer1==customer2));
       }
       catch (Exception e)
       {
           if(tx!=null)
           {
                tx.rollback();
           }
       }
       finally
       {
           session.close();
       }
    }
}

 

程序控制臺輸出結果:

 

 

Hibernate:
   select
       customer0_.id as id0_0_,
       customer0_.username as username0_0_,
       customer0_.balance as balance0_0_
   from
       customer customer0_
   where
       customer0_.id=?
customer.getUsername is lisi
-------------------------------------
customer2.getUsername is lisi
-------------------------------------
customer1 == customer2 result is true


其原理是:在同一個Session裏面,第一次調用get()方法, Hibernate先檢索緩存中是否有該查找對象,發現沒有,Hibernate發送SELECT語句到數據庫中取出相應的對象,然後將該對象放入緩存中,以便下次使用,第二次調用get()方法,Hibernate先檢索緩存中是否有該查找對象,發現正好有該查找對象,就從緩存中取出來,不再去數據庫中檢索,沒有再次發送select語句。

 

數據從緩存中清除:

 

1. evict()將指定的持久化對象從緩存中清除,釋放對象所佔用的內存資源,指定對象從持久化狀態變爲脫管狀態,從而成爲遊離對象。

2. clear()將緩存中的所有持久化對象清除,釋放其佔用的內存資源。

evict()方法是針對指定的持久化對象,clear()是全部

 

其他緩存操作:

 

1. contains()判斷指定的對象是否存在於緩存中。

2. flush()刷新緩存區的內容,使之與數據庫數據保持同步。

 

 

1.2、       二級緩存

 

@Test
 
public void testCache2() {
 
Session session1 = sf.openSession();//獲得Session1
 
session1.beginTransaction();
 
Category c = (Category)session1.load(Category.class, 1);
 
System.out.println(c.getName());
 
 
 
 
 
session1.getTransaction().commit();
 
session1.close();
 
 
 
Session session2 = sf.openSession();//獲得Session2
 
session2.beginTransaction();
 
Category c2 = (Category)session2.load(Category.class, 1);
 
System.out.println(c2.getName());
 
session2.getTransaction().commit();
 
session2.close();
 
}
 


當我們重啓一個Session,第二次調用load或者get方法檢索同一個對象的時候會重新查找數據庫,會發select語句信息。

 

原因:一個session不能取另一個session中的緩存。

 

性能上的問題:假如是多線程同時去取Category這個對象,load一個對象,這個對像本來可以放到內存中的,可是由於是多線程,是分佈在不同的session當中的,所以每次都要從數據庫中取,這樣會帶來查詢性能較低的問題。

 

解決方案:使用二級緩存。

 

1.什麼是二級緩存?

 

SessionFactory級別的緩存,可以跨越Session存在,可以被多個Session所共享。

 

2.適合放到二級緩存中:

 

(1)經常被訪問

 

(2)改動不大

 

(3)數量有限

 

(4)不是很重要的數據,允許出現偶爾併發的數據。

 

這樣的數據非常適合放到二級緩存中的。

 

用戶的權限:用戶的數量不大,權限不多,不會經常被改動,經常被訪問。

 

例如組織機構。

 

 

 

思考:什麼樣的類,裏面的對象才適合放到二級緩存中?

 

改動頻繁,類裏面對象特別多,BBS好多帖子,這些帖子20000多條,哪些放到緩存中,不能確定。除非你確定有一些經常被訪問的,數據量並不大,改動非常少,這樣的數據非常適合放到二級緩存中的。

 

 

3.二級緩存實現原理:

 

  Hibernate如何將數據庫中的數據放入到二級緩存中?注意,你可以把緩存看做是一個Map對象,它的Key用於存儲對象OID,Value用於存儲POJO。首先,當我們使用Hibernate從數據庫中查詢出數據,獲取檢索的數據後,Hibernate將檢索出來的對象的OID放入緩存中key 中,然後將具體的POJO放入value中,等待下一次再次向數據查詢數據時,Hibernate根據你提供的OID先檢索一級緩存,若有且配置了二級緩存,則檢索二級緩存,如果還沒有則才向數據庫發送SQL語句,然後將查詢出來的對象放入緩存中。

 

4使用二級緩存

 

(1)打開二級緩存:

 

爲Hibernate配置二級緩存:

 

在主配置文件中hibernate.cfg.xml:

 

 

<!-- 使用二級緩存 -->
 
<propertyname="cache.use_second_level_cache">true</property>
 
 <!--設置緩存的類型,設置緩存的提供商-->
 
<property   
 
 name="cache.provider_class">org.hibernate.cache.EhCacheProvider
 
</property>

 

(2)配置ehcache.xml

 

<ehcache>
 
   <!--
       緩存到硬盤的路徑
   -->
 
   <diskStore path="d:/ehcache"/>
 
 
 
   <defaultCache
 
       maxElementsInMemory="200"<!-- 最多緩存多少個對象 -->
 
       eternal="false"<!-- 內存中的對象是否永遠不變 -->
 
       timeToIdleSeconds="50"<!--發呆了多長時間,沒有人訪問它,這麼長時間清除 -->
 
       timeToLiveSeconds="60"<!--活了多長時間,活了1200秒後就可以拿走,一般Live要比Idle設置的時間長 -->
 
       overflowToDisk="true"<!--內存中溢出就放到硬盤上 -->
 
       />
 
   <!--
       指定緩存的對象,緩存哪一個實體類
       下面出現的的屬性覆蓋上面出現的,沒出現的繼承上面的。
   -->
 
   <cache name="com.suxiaolei.hibernate.pojos.Order"
 
       maxElementsInMemory="200"
 
       eternal="true"
 
       timeToIdleSeconds="0"
 
       timeToLiveSeconds="0"
 
        overflowToDisk="false"
 
       />
 
</ehcache>


 

(3)使用二級緩存需要在實體類中加入註解:

 

需要ehcache-1.2.jar包:

 

還需要commons_loging1.1.1.jar包

 

@Cache(usage =CacheConcurrencyStrategy.READ_WRITE)

 

Load默認使用二級緩存,就是當查一個對象的時候,它先會去二級緩存裏面去找,如果找到了就不去數據庫中查了。

 

Iterator默認的也會使用二級緩存,有的話就不去數據庫裏面查了,不發送select語句了。

 

List默認的往二級緩存中加數據,假如有一個query,把數據拿出來之後會放到二級緩存,但是執行查詢的時候不會到二級緩存中查,會在數據庫中查。原因每個query中查詢條件不一樣。

 

(4)也可以在需要被緩存的對象中hbm文件中的<class>標籤下添加一個<cache>子標籤:

 

 
<hibernate-mapping>
       <class name="com.suxiaolei.hibernate.pojos.Order"table="orders">
           <cache usage="read-only"/>
           <id name="id" type="string">
                <columnname="id"></column>
                <generatorclass="uuid"></generator>
           </id>
          
           <property name="orderNumber" column="orderNumber"type="string"></property>
           <property name="cost" column="cost"type="integer"></property>
          
           <many-to-one name="customer"class="com.suxiaolei.hibernate.pojos.Customer"
                        column="customer_id"cascade="save-update">
           </many-to-one>      
       </class>
   </hibernate-mapping>


 

存在一對多的關係,想要在在獲取一方的時候將關聯的多方緩存起來,需要再集合屬性下添加<cache>子標籤,這裏需要將關聯的對象的hbm文件中必須在存在<class>標籤下也添加<cache>標籤,不然Hibernate只會緩存OID。

 

 <hibernate-mapping>
       <class name="com.suxiaolei.hibernate.pojos.Customer"table="customer">
           <!-- 主鍵設置 -->
           <id name="id" type="string">
                <columnname="id"></column>
                <generatorclass="uuid"></generator>
           </id>
          
           <!-- 屬性設置 -->
           <property name="username" column="username"type="string"></property>
           <property name="balance" column="balance"type="integer"></property>
          
           <set name="orders" inverse="true"cascade="all" lazy="false" fetch="join">
                <cacheusage="read-only"/>
                <keycolumn="customer_id" ></key>
                <one-to-manyclass="com.suxiaolei.hibernate.pojos.Order"/>
           </set>
       </class>
   </hibernate-mapping>



一級緩存是用於同一個Session中共享數據的,減少往數據庫查詢次數,當不同Session頻繁訪問數據庫的同一張表時,一級緩存就不明顯了,需要使用二級緩存,二級緩存是用於不同Session中共享數據的,在多線程頻繁查詢數據庫,更新數據庫方面有較好的效果

 

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章