hibernate緩存:一級緩存和二級緩存


 

1.什么是緩存?

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

 

緩存策略提供商:

提供了HashTable緩存,EHCacheOSCacheSwarmCachejBoss Cathe2,這些緩存機制,其中EHCacheOSCache是不能用於集群環境(Cluster Safe)的,而SwarmCachejBoss Cathe2是可以的。HashTable緩存主要是用來測試的,只能把對象放在內存中,EHCacheOSCache可以把對象放在內存(memory)中,也可以把對象放在硬盤(disk)上(為什么放到硬盤上?上面解釋了)。

 

Hibernate緩存分類:

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

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

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

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

一級緩存:

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

數據放入緩存:

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

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

3. 使用HQLQBC等從數據庫中查詢數據。

例如:數據庫有一張表如下:

 

使用get()load()證明緩存的存在:

 

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.getUsername is"+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.getUsername is"+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 islisi
-------------------------------------
customer2.getUsername islisi
-------------------------------------
customer1 == customer2 result is true

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

數據從緩存中清除:

1. evit()將指定的持久化對象從緩存中清除,釋放對象所占用的內存資源,指定對象從持久化狀態變為脫管狀態,從而成為游離對象。 
2. clear()將緩存中的所有持久化對象清除,釋放其占用的內存資源。

其他緩存操作:

1. contains()判斷指定的對象是否存在於緩存中。
2. flush()刷新緩存區的內容,使之與數據庫數據保持同步

 

二級緩存:

@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用於存儲對象OIDValue用於存儲POJO。首先,當我們使用Hibernate從數據庫中查詢出數據,獲取檢索的數據后,Hibernate將檢索出來的對象的OID放入緩存中key 中,然后將具體的POJO放入value中,等待下一次再次向數據查詢數據時,Hibernate根據你提供的OID先檢索一級緩存,若有且配置了二級緩存,則檢索二級緩存,如果還沒有則才向數據庫發送SQL語句,然后將查詢出來的對象放入緩存中。

 

4.使用二級緩存

(1)打開二級緩存:

Hibernate配置二級緩存:

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

 <!-- 使用二級緩存 -->

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

或者當hibernate與Spring整合后直接配到Spring配置文件applicationContext.xml中

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
     <property name="dataSource" ref="dataSource"/>
     <property name="mappingResources">
        <list>
          <value>com/lp/ecjtu/model/Employee.hbm.xml</value>
          <value>com/lp/ecjtu/model/Department.hbm.xml</value>
        </list>
     </property>
     <property name="hibernateProperties">
        <value>
        hibernate.dialect=org.hibernate.dialect.OracleDialect
        hibernate.hbm2ddl.auto=update
        hibernate.show_sql=true
        hibernate.format_sql=true    
        hibernate.cache.use_second_level_cache=true
        hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider
         hibernate.generate_statistics=true           
     </value>
    </property>
</bean>

(2)配置ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!--
        緩存到硬盤的路徑
    -->
    <diskStore path="d:/ehcache"></diskStore>
    
    <!--
        默認設置
        maxElementsInMemory : 在內存中最大緩存的對象數量。
        eternal : 緩存的對象是否永遠不變。
        timeToIdleSeconds :可以操作對象的時間。
        timeToLiveSeconds :緩存中對象的生命周期,時間到后查詢數據會從數據庫中讀取。
        overflowToDisk :內存滿了,是否要緩存到硬盤。
    -->
    <defaultCache maxElementsInMemory="200" eternal="false" 
        timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></defaultCache>
        
    <!--
        指定緩存的對象。
        下面出現的的屬性覆蓋上面出現的,沒出現的繼承上面的。
    -->
    <cache name="com.suxiaolei.hibernate.pojos.Order" maxElementsInMemory="200" eternal="false" 
        timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></cache>

</ehcache>

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

需要ehcache-1.2.3.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">
                <column name="id"></column>
                <generator class="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">
                <column name="id"></column>
                <generator class="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">
                <cache usage="read-only"/>
                <key column="customer_id" ></key>
                <one-to-many class="com.suxiaolei.hibernate.pojos.Order"/>
            </set>
        </class>
    </hibernate-mapping>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM