1. 程式人生 > 其它 >hibernate快取:一級快取和二級快取

hibernate快取:一級快取和二級快取

1.什麼是快取?

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

快取策略提供商:

提供了HashTable快取,EHCache,OSCache,SwarmCache,jBossCathe2,這些快取機制,其中EHCache,OSCache是不能用於叢集環境(ClusterSafe)的,而SwarmCache,jBossCathe2是可以的。HashTable快取主要是用來測試的,只能把物件放在記憶體中,EHCache,OSCache可以把物件放在記憶體(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.使用HQL和QBC等從資料庫中查詢資料。

例如:資料庫有一張表如下:

使用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_.idasid0_0_,
customer0_.usernameasusername0_0_,
customer0_.balanceasbalance0_0_
from
customercustomer0_
where
customer0_.id=?
customer.getUsernameislisi
-------------------------------------
customer2.getUsernameislisi
-------------------------------------
customer1==customer2resultistrue

其原理是在同一個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用於儲存物件OID,Value用於儲存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>