1. 程式人生 > >Hibernate之快取詳解

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查詢

<span style="font-size:18px;"><strong>    public void Query(){
        Session sess = HibernateSessionFactory.getSession();
        Transaction tx = sess.beginTransaction();
        Student s1 = (Student)sess.get(Student.class, 2);
        System.out.println(s1.getName());
        Student s2 = (Student)sess.get(Student.class, 2);
        System.out.println(s2.getName());
        tx.commit();
        HibernateSessionFactory.closeSession();
    }</strong></span>
上面的兩次查詢,第一次執行了get方法查詢了資料庫,產生了一條sql語句,第二次執行get方法時,由於在一級快取中找到了該物件,因此不會查詢資料庫,不再發出sql語句;
2>開啟兩個session中發出兩次get查詢

<span style="font-size:18px;"><strong>        public void Query(){
        Session sess1 = HibernateSessionFactory.getSession();
        Transaction tx1 = sess1.beginTransaction();
        Student s1 = (Student)sess1.get(Student.class, 2);
        System.out.println(s1.getName());
        tx1.commit();
        HibernateSessionFactory.closeSession();
        Session sess2 = HibernateSessionFactory.getSession();
        Transaction tx2 = sess2.beginTransaction();
        Student s2 = (Student)sess2.get(Student.class, 2);
        System.out.println(s2.getName());
        tx2.commit();
        HibernateSessionFactory.closeSession();
    }</strong></span>
上面的兩次查詢,兩次執行get方法時都查詢了資料庫,產生了兩條sql語句,原因在於,第一次執行get方法查詢出結果後,關閉了session,快取被清除了,第二次執行get方法時,從快取中找不到結果,只能到資料庫查詢;
2,iterate查詢測試

插入一個iteritor查詢方式:

<span style="font-size:18px;"><strong>    public void Query(){
        Session sess = HibernateSessionFactory.getSession();
        Transaction tx = sess.beginTransaction();
        Query query = sess.createQuery("from Student");
        Iterator iter = query.iterate();
        while(iter.hasNext()){
               System.out.println(((Student)iter.next()).getName());
        }
        tx.commit();
        HibernateSessionFactory.closeSession();
    }</strong></span>
<span style="font-size:18px;"><strong>    public void Query(){
        Session sess = HibernateSessionFactory.getSession();
        Transaction tx = sess.beginTransaction();
        Student s1 = (Student)sess.createQuery("from Student s where s.id = 2").iterate().next();
        System.out.println(s1.getName());
        Student s2 = (Student)sess.createQuery("from Student s where s.id = 2").iterate().next();
        System.out.println(s2.getName());
        tx.commit();
        HibernateSessionFactory.closeSession();
    }</strong></span>
上面的程式碼執行後會生成三條sql語句,第一次執行iterate().next()時會發出查詢id的sql語句(第一條sql語句),得到s1物件,使用s1物件獲得name屬性值時會發出相應的查詢實體物件的sql語句(第二條sql語句),第二次執行iterate().next()時會發出查詢id的sql語句(第三條sql語句),但是不會發出查詢實體物件的sql語句,因為hibernate使用快取,不會發出sql語句
3,iterate查詢屬性測試:

<span style="font-size:18px;"><strong>    public void Query(){
        Session sess = HibernateSessionFactory.getSession();
        Transaction tx = sess.beginTransaction();
        String name1 = (String)sess.createQuery("select s.name from Student s where s.id = 2").iterate().next();
        System.out.println(name1);
        String name2 = (String)sess.createQuery("select s.name from Student s where s.id = 2").iterate().next();
        System.out.println(name2);
        tx.commit();
        HibernateSessionFactory.closeSession();
    }</strong></span>
上面的程式碼第一次執行iterate().next()時發出查詢屬性的sql語句,第二次執行iterate().next()時也會發出查詢屬性的sql語句,這是因為iterate查詢普通屬性,一級快取不會快取,所以會發出sql;
4,在一個session中先save,再執行load查詢

<span style="font-size:18px;"><strong>    public void Query(){
        Session sess = HibernateSessionFactory.getSession();
        Transaction tx = sess.beginTransaction();
        Student s = new Student(8, "newAcc", 8, 8);
        Serializable id = sess.save(s);
        tx.commit();
        Student s1 = (Student)sess.load(Student.class, 8);
        System.out.println(s1.getName());
        HibernateSessionFactory.closeSession();
    }</strong></span>
上面的程式碼執行save操作時,它會在快取裡放一份,執行load操作時,不會發出sql語句,因為save使用了快取;
--------------------

Session介面為應用程式提供了兩個管理快取的方法:

1>evict()方法:用於將某個物件從Session的一級快取中清除;

2>clear()方法:用於將一級快取中的所有物件全部清楚;

測試:

<span style="font-size:18px;"><strong>    public void Query(){
        Session sess = HibernateSessionFactory.getSession();
        Transaction tx = sess.beginTransaction();
        Student s = (Student)sess.load(Student.class, 1);
        System.out.println(s.getName());
        sess.clear();//清除一級快取中的所有物件
        Student s1 = (Student)sess.load(Student.class, 1);
        System.out.println(s1.getName());
        tx.commit();
        HibernateSessionFactory.closeSession();
    }</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

<span style="font-size:18px;"><strong><ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />
    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />
    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        />
</ehcache></strong></span>
在上述配置中,diskStore元素設定快取資料檔案的儲存目錄;defaultCache元素設定快取的預設資料過期策略;cache元素設定具體的命名快取的資料過期策略。每個命名快取代表一個快取區域,命名快取機制允許使用者在每個類以及類的每個集合的粒度上設定資料過期策略;
在defaultCache元素中,maxElementsInMemory屬性設定快取物件的最大數目;eternal屬性指定是否永不過期,true為不過期,false為過期;timeToldleSeconds屬性設定物件處於空閒狀態的最大秒數;timeToLiveSeconds屬性設定物件處於快取狀態的最大秒數;overflowToDisk屬性設定記憶體溢位時是否將溢位物件寫入硬碟;
3>在Hibernate配置檔案裡面啟用EHCache

在hibernate.cfg.xml配置檔案中,啟用EHCache的配置如下:

<span style="font-size:18px;"><strong>                <!-- 啟用二級快取 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- 設定二級快取外掛EHCache的Provider類 -->
        <property name="hibernate.cache.region.factory_class">
        org.hibernate.cache.ehcache.EhCacheRegionFactory</property></strong></span>
4>配置哪些實體類的物件需要二級快取,有兩種方式:
1>>在實體類的對映檔案裡面配置

在需要進行快取的持久化物件的對映檔案中配置相應的二級快取策略,如User,hbm.xml:

<span style="font-size:18px;"><strong><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.hibtest1.entity.User" table="user" catalog="bookshop">
        <cache usage="read-write"/>
        <id name="id" type="java.lang.Integer">
            <column name="Id" />
            <generator class="native" />
        </id>
        <property name="loginName" type="java.lang.String">
            <column name="LoginName" length="50" />
        </property>
        
    </class>
</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>元素來配置哪些實體類的物件需要二級快取:

<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的測試:

<span style="font-size:18px;"><strong>    public void Query(){
        Session sess1 = HibernateSessionFactory.getSession();
        Student s1 = (Student)sess1.get(Student.class, 1);
        System.out.println(s1.getName());
        HibernateSessionFactory.closeSession();
        Session sess2 = HibernateSessionFactory.getSession();
        Student s2 = (Student)sess2.get(Student.class, 1);
        System.out.println(s2.getName());
        HibernateSessionFactory.closeSession();
    }</strong></span>
上面的程式碼,第一次執行get方法查詢出結果後,關閉了session,一級快取被清除了,由於配置並啟用了二級快取,查詢出的結果會放入二級快取,第二次執行get方法時,首先從一級快取中查詢,沒有找到,然後轉到二級快取查詢,二級快取中找到結果,就不需要從資料庫查詢了。
注意:在hibernate配置二級快取時屬性的順序如下,順序錯了會空指標異常:

<span style="font-size:18px;"><strong>                <!-- 啟用二級快取 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- 設定二級快取外掛EHCache的Provider類 -->
        <property name="hibernate.cache.region.factory_class">
        org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
        <mapping class="com.anlw.entity.Student"/>
        <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>

<span style="font-size:18px;"><strong>    public static void query(){
        Session sess = HibernateSessionFactory.getSession();
        Transaction tx = sess.beginTransaction();
        Query query = sess.createQuery("select s.name from Student s");
        query.setCacheable(true);
        List names = query.list();
        for(Iterator iter = names.iterator();iter.hasNext();){
            String name = (String)iter.next();
            System.out.println(name);
        }
        System.out.println("----------");
        query = sess.createQuery("select s.name from Student s");
        query.setCacheable(true);
        names = query.list();
        for(Iterator iter = names.iterator();iter.hasNext();){
            String name = (String)iter.next();
            System.out.println(name);
        }
        tx.commit();
        HibernateSessionFactory.closeSession();
    }
</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>

<span style="font-size:18px;"><strong>    public static void query(){
        Session sess1 = HibernateSessionFactory.getSession();
        Transaction tx1 = sess1.beginTransaction();
        Query query = sess1.createQuery("select s.name from Student s");
        query.setCacheable(true);
        List names = query.list();
        for(Iterator iter = names.iterator();iter.hasNext();){
            String name = (String)iter.next();
            System.out.println(name);
        }
        tx1.commit();
        HibernateSessionFactory.closeSession();
        System.out.println("----------");
        Session sess2 = HibernateSessionFactory.getSession();
        Transaction tx2 = sess2.beginTransaction();
        query = sess2.createQuery("select s.name from Student s");
        query.setCacheable(true);
        names = query.list();
        for(Iterator iter = names.iterator();iter.hasNext();){
            String name = (String)iter.next();
            System.out.println(name);
        }
        tx2.commit();
        HibernateSessionFactory.closeSession();
    }</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>

<span style="font-size:18px;"><strong>    public static void query(){
        Session sess1 = HibernateSessionFactory.getSession();
        Transaction tx1 = sess1.beginTransaction();
        Query query = sess1.createQuery("from Student");
        query.setCacheable(true);
        List student = query.list();
        for(Iterator iter = student.iterator();iter.hasNext();){
            Student s = (Student)iter.next();
            System.out.println(s.getName()+"--"+s.getAge());
        }
        tx1.commit();
        HibernateSessionFactory.closeSession();
        System.out.println("----------");
        Session sess2 = HibernateSessionFactory.getSession();
        Transaction tx2 = sess2.beginTransaction();
        query = sess2.createQuery("from Student");
        query.setCacheable(true);
        student = query.list();
        for(Iterator iter = student.iterator();iter.hasNext();){
            Student s = (Student)iter.next();
            System.out.println(s.getName()+"--"+s.getAge());
        }
        tx2.commit();
        HibernateSessionFactory.closeSession();
    }</strong></span>
查詢結果如下:
<span style="font-size:18px;"><strong>Hibernate: 
    select
        student0_.id as id1_0_,
        student0_.age as age2_0_,
        student0_.name as name3_0_ 
    from
        student student0_
anliwenaaa--1
test--2
----------
Hibernate: 
    select
        student0_.id as id1_0_0_,
        student0_.age as age2_0_0_,
        student0_.name as name3_0_0_ 
    from
        student student0_ 
    where
        student0_.id=?
Hibernate: 
    select
        student0_.id as id1_0_0_,
        student0_.age as age2_0_0_,
        student0_.name as name3_0_0_ 
    from
        student student0_ 
    where
        student0_.id=?
anliwenaaa--1
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到二級快取中取得相應的資料;
hibernate