1. 程式人生 > >Hibernate二級快取問題

Hibernate二級快取問題

相關概念和定義
1、快取的意義
把一些不常修改,但是又經常用的資料存放到記憶體中,這樣能減少與資料庫的互動,提升程式的效能

2、Hibernate中提供了兩級快取:
第一級別的快取是Session級別的快取(比如說在呼叫get方法的時候,如果已經查詢過一次了,第二次就不會查了,而是直接返回session快取中已經存在的那個物件給你,不過這個只對當前Session有效,一旦又開一個新的Session的話,呼叫get方法獲取資料時還是會再次去查詢資料庫的)。這一級別的快取由hibernate 管理的,一般情況下無需進行干預

第二級別的快取是SessionFactory 級別的快取,也就是hibernate二級快取,它是屬於程序範圍的快取

3、SessionFactory的快取:
內建快取: Hibernate 自帶的, 不可解除安裝. 通常在 Hibernate 的初始化階段, Hibernate 會把對映元資料和預定義的 SQL 語句放到 SessionFactory 的快取中, 對映元資料是對映檔案中資料的複製, 而預定義 SQL 語句是 Hibernate 根據對映元資料推倒出來的. 該內建快取是隻讀的.

外接快取(二級快取): 一個可配置的快取外掛. 預設情況下 SessionFactory 不會啟動二級快取,需要使用者自己匯入第三方外掛,在hibernate.cfg.xml檔案中通過配置開啟二級快取。外接快取中的資料是資料庫資料的複製, 外接快取的物理介質可以是記憶體或硬碟

4、二級快取的記憶體結構
二級快取由快取提供者提供(CacheProvider),包含四部分:類快取區、集合快取區、查詢快取區、更新時間戳

 

5、二級快取的併發訪問策略
 

 

6、快取中存放的資料
適合放入二級快取中的資料:

很少被修改

不是很重要的資料, 允許出現偶爾的併發問題

不適合放入二級快取中的資料:

經常被修改

財務資料, 絕對不允許出現併發問題

與其他應用資料共享的資料

7、快取提供的供應商
Hibernate 的二級快取是程序或叢集範圍內的快取, 快取中存放的是物件的散裝資料

二級快取是可配置的的外掛,Hibernate 允許選用以下型別的快取外掛:

EHCache: 可作為程序範圍內的快取, 存放資料的物理介質可以是記憶體或硬碟, 對 Hibernate 的查詢快取提供了支援

OpenSymphony `:可作為程序範圍內的快取, 存放資料的物理介質可以是記憶體或硬碟, 提供了豐富的快取資料過期策略, 對 Hibernate 的查詢快取提供了支援

SwarmCache: 可作為叢集範圍內的快取, 但不支援 Hibernate的查詢快取

JBossCache:可作為叢集範圍內的快取, 支援 Hibernate 的查詢快取

4 種快取外掛支援的併發訪問策略

15.2EHCache配置步驟
1、匯入jar包及依賴jar包:

ehcache-1.5.0.jar依賴backport-util-concurrent 和 commons-logging

 

2、在hibernate.cfg.xml中開啟二級快取

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

 

3、在hibernate.cfg.xml指定快取供應商

<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

 

4、配置哪些資料使用二級快取,不配置的話二級快取不會快取任何資料(在hibernate.cfg.xml檔案裡和對映檔案裡配置2選1,需要注意的是這些配置必須配置在對映檔案的後面,具體參考hibernate-configuration-3.0.dtd)

第一種方式:

在hibernate.cfg.xml中配置

<!-- 配置類級別的二級快取:這個必須放在對映檔案引入的後面 -->

<class-cache usage="read-write" class="com.itheima.domain.Department"/>

<class-cache usage="read-write" class="com.itheima.domain.Employee"/>

<!-- 配置集合級別的二級快取 -->

<collection-cache usage="read-write" collection="com.itheima.domain.Department.emps"/>

 

第二種方式:

Department.hbm.xml

<class name="Department" table="DEPARTMENT">

           <!-- 類級別的二級快取:必須放在class節點的第一個位置 -->

           <cache usage="read-write"/>

        <id name="id">

            <generator class="native"/>

        </id>

        <property name="name" type="string" column="NAME" />

        <set name="emps" table="Employee">

               <!-- 集合級別的二級快取 -->

               <cache usage="read-write"/>

               <key column="DEPT_ID"></key>

               <one-to-many class="Employee" />

        </set>

    </class>

 

Employee.hbm.xml

<class name="Employee" table="EMPLOYEE">

        <!-- 配置類級別的二級快取 -->

        <cache usage="read-write"/>

        <id name="id">

            <generator class="native"/>

        </id>

        <property name="name" type="string" column="NAME" />

         <many-to-one name="depart">

                <column name="DEPT_ID"></column>

         </many-to-one>

    </class>

 

5、配置ehcache預設的配置檔案ehcache.xml(名字固定)(放在類路徑下)

可以拷貝ehcache-1.5.0.jar包中的ehcache-failsafe.xml,然後改名為ehcache.xml

ehcache.xml內容:除了硬碟儲存目錄,其他內容基本上不需要更改

<diskStore path="java.io.tmpdir"/>     

<!--硬碟預設快取目錄(C:\Users\wzhting\AppData\Local\Temp\)-->

<!--我們一般都用下面的預設資料過期策略-->

<defaultCache           

        maxElementsInMemory="10000"

        eternal="false"

        timeToIdleSeconds="120"

        timeToLiveSeconds="120"

        overflowToDisk="true"

        maxElementsOnDisk="10000000"

        diskPersistent="false"

        diskExpiryThreadIntervalSeconds="120"

        memoryStoreEvictionPolicy="LRU"

        />

 

也可以自己設定過期策略,下面詳細解釋一下快取策略的配置中每個屬性的意思:

 

15.3類級別的二級快取(Class Cache)
所謂類級別的二級快取,就是查詢出的一個實體類物件會放入二級快取

例一、編寫測試用例證明資料存入了二級快取

public void testClassCache(){

              //第一次從資料庫中查詢一個部門的資料,會發送一條sql語句

              Session s1 = HibernateUtil.getCurrentSession();

              Transaction tx1 = s1.beginTransaction();

              Department d1 = (Department)s1.get(Department.class, 1);

              System.out.println(d1.getName());

              tx1.commit();

             

              //第二次查詢一個部門的資料,由於s1已經關閉,而且已經配置了二級快取,所以不會再發送sql

              //語句,而是直接從二級快取中去取,如果s1每關閉呢?那麼會優先從一級快取中取這個部門的資料

              Session s2 = HibernateUtil.getCurrentSession();

              Transaction tx2 = s2.beginTransaction();

              Department d2 = (Department)s2.get(Department.class, 1);

              System.out.println(d2.getName());

              tx2.commit();

       }

 

分析:

第一次查詢時,把資料存放到一級快取和二級快取中,但存放形式不一樣。一級快取存放的是實體物件的引用(即記憶體地址),而二級快取的類快取區存放的是物件中的資料(雜湊資料id:1 name:d1name)。

一級快取沒有關閉的情況下,再次查詢同樣的實體記錄,返回的是物件的引用,因此兩次從一級快取中取出的物件記憶體地址一致。而一級快取關閉後,從二級快取中取出的資料因為是雜湊資料,需要重新封裝到新物件中,所以,記憶體地址會不同。

以下程式碼可以再次證明:

public void testClassCache(){

              //第一次從資料庫中查詢一個部門的資料,會發送一條sql語句

              Session s1 = HibernateUtil.getCurrentSession();

              Transaction tx1 = s1.beginTransaction();

              Department d1 = (Department)s1.get(Department.class, 1);

              System.out.println(d1);

             

              //第二次查詢一個部門的資料,由於s1已經關閉,而且已經配置了二級快取,所以不會再發送sql

              //語句,而是直接從二級快取中去取,如果s1每關閉呢?那麼會優先從一級快取中取這個部門的資料

              Session s2 = HibernateUtil.getCurrentSession();

              Department d2 = (Department)s2.get(Department.class, 1);

              System.out.println(d2);

              //這裡才開始提交事務哦親:s1和s2用的是同一個事務,同一個事務內,相同的資料是會從一級快取裡拿的,

              //所以控制檯看到的兩個Department物件的記憶體地址是一樣的

              tx1.commit();

             

              //還是不會從資料庫裡去查詢,證明了雜湊資料的存在

              Session s3 = HibernateUtil.getCurrentSession();

              Transaction tx3 = s3.beginTransaction();

              Department d3 = (Department)s3.get(Department.class, 1);

              System.out.println(d3);

              tx3.commit();

       }

 

結果:

Hibernate:

    select

        department0_.id as id0_0_,

        department0_.NAME as NAME0_0_

    from

        DEPARTMENT department0_

    where

        department0_.id=?

<!--以下是通過s1獲取到的Department物件的記憶體地址-->

[email protected]

<!--以下是通過s2獲取到的Department物件的記憶體地址-->

[email protected]

<!--以下是通過s3獲取到的Department物件的記憶體地址-->

[email protected]

 

例二、get和load 可以讀取類級別二級快取,但是從資料庫裡查詢出的一個集合的資料就只能存,不能取了,這個集合(直接用session.createQuery、session.createCriteria、session.createSQLQuery查出來的集合)要跟集合級別的二級快取區分開來,集合級別的二級快取說的是一個實體類中有一個集合屬性(比如說部門的實體類中的員工的集合屬性),這個集合查出來後會存入集合級別的二級快取,兩者概念不能混淆了

   /**

        * 測試查詢出來的集合是否會存入二級快取

        * 結論:不管是Query、Criteria、還是原生態的sql查出來的集合資料不會存入二級緩

        * 存,但是集合中的每個物件會存入二級快取

        */

       @SuppressWarnings("unchecked")

       @Test

       public void testCollCache(){

              //第一次獲取集合資料

              Session s1 = HibernateUtil.getCurrentSession();

              Transaction tx1 = s1.beginTransaction();

              //List<Employee> emps1 = s1.createQuery("from Employee").list();

              //List<Employee> emps1 = s1.createCriteria(Employee.class).list();

              List<Employee> emps1 = s1.createSQLQuery("select * from employee").list();

              System.out.println(emps1.size());

              tx1.commit();

             

              //第二次獲取集合資料:又從資料庫中取了一次

              Session s2 = HibernateUtil.getCurrentSession();

              Transaction tx2 = s2.beginTransaction();

              //List<Employee> emps2 = s2.createQuery("from Employee").list();

              //List<Employee> emps2 = s2.createCriteria(Employee.class).list();

              List<Employee> emps2 = s2.createSQLQuery("select * from employee").list();

              System.out.println(emps2.size());

              tx2.commit();

             

              //獲取集合中的一個物件的資料:沒有從資料庫去取,說明集合中的物件已經都存入二級快取了

              Session s3 = HibernateUtil.getCurrentSession();

              Transaction tx3 = s3.beginTransaction();

              Employee e = (Employee)s3.get(Employee.class, 1);

              System.out.println(e.getName());

              tx3.commit();

       }

 

問題:如果我確實有快取集合這樣的需求呢?就是說我用session.createQuery、session.createCriteria、session.createSQLQuery從資料庫裡查出來的集合想要進行快取,怎麼辦?

答:請參看15.6查詢快取,這裡面詳細解釋瞭如何進行集合的快取

 

15.4集合級別的二級快取(Collection Cache)
例一、集合級別二級快取測試

我要測試的東西很明確,就是說當我用一個session查詢出一個部門後(這個部門實體類中有一個員工的集合屬性Set<Employee>),用第二個session再次去查詢這同一個部門的話,還會不會再去資料庫裡查一次,當我獲取這個部門中的員工集合的時候,會不會再去資料庫裡查一次?下面我們準備好測試環境和程式碼看演示效果:

準備測試環境:配置hibernate.cfg.xml(在實體類.hbm.xml中配置也行,詳情參見15.2EHCache配置步驟)

<!-- 配置類級別的二級快取 -->

<class-cache usage="read-write" class="com.itheima.domain.Department"/>

<class-cache usage="read-write" class="com.itheima.domain.Employee"/>

<!-- 配置集合級別的二級快取 -->

<collection-cache usage="read-write" collection="com.itheima.domain.Department.emps"/>

 

 

    /**

        * 測試實體類中的集合是否會存入二級快取:也就是集合級別的資料是否會存入二級快取

        * 結論:實體類中的集合屬性的資料會存入集合級別的二級快取

        */

       @Test

       public void testCollInEntity(){

              Session s1 = HibernateUtil.getCurrentSession();

              Transaction tx1 = s1.beginTransaction();

              //存入類級別的二級快取

              Department dept1 = (Department)s1.get(Department.class, 1);

              //存入集合級別的二級快取

              Set<Employee> emps1 = dept1.getEmps();

              for(Employee e:emps1){

                     System.out.println(e.getName());

              }

              System.out.println("---------------------------");

              tx1.commit();

             

              Session s2 = HibernateUtil.getCurrentSession();

              Transaction tx2 = s2.beginTransaction();

              //從類級別的二級快取中取出資料

              Department dept2 = (Department)s2.get(Department.class, 1);

              //從集合級別的二級快取中取出資料

              Set<Employee> emps2 = dept2.getEmps();

              for(Employee e:emps2){

                     System.out.println(e.getName());

              }

              tx2.commit();

       }

 

集合區的資料存放原理

集合區的資料存放原理結論:

由圖可知,實體類中的集合屬性的資料在儲存時分為兩部分,集合中每個物件的oid儲存在集合快取區,每個物件的具體的屬性值資料儲存在類級別的快取區,當需要用的時候根據oid再次從類級別的快取區中獲取資料進行封裝

小疑問

hibernate.cfg.xml:註釋掉

<class-cache usage="read-write" class="com.itheima.domain.Employee"/>

請問會發生什麼?

答:當第二次用這個部門中的員工集合的資料的時候會再次從資料庫中去查詢

 

例二、一級快取的更新會自動同步到二級快取中

當一級快取中的資料更新後,是會同步到二級快取中去的,下面我們測試一下這個結論:

/**

        * 測試一級快取中的資料更新後,會不會同步更新到二級快取

        * 結論:會

        */

       @Test

       public void cacheLevel1ToLevel2(){

              Session s1 = HibernateUtil.getCurrentSession();

              Transaction tx1 = s1.beginTransaction();

              //存入一級快取和類級別的二級快取

              Department dept1 = (Department)s1.get(Department.class, 1);

              //這時候我們更新以下dept1中的資料,其實這個操作就是更新一級快取中的資料,因為我們操作的是持久化物件

              dept1.setName("hhhhh");

              //當事務提交的時候,會同步將資料更新到二級快取

              tx1.commit();

             

              Session s2 = HibernateUtil.getCurrentSession();

              Transaction tx2 = s2.beginTransaction();

              //從類級別的二級快取中取出部門資料

              Department dept2 = (Department)s2.get(Department.class, 1);

              //如果列印結果是hhhhhh,那說明一級快取中的更新確實會同步到二級快取中去,結果是肯定的

              System.out.println(dept2.getName());

              tx2.commit();

       }

 

 

示例:超過記憶體快取數量會快取到硬碟

maxElementsInMemory=5,查詢所有員工(通過加斷點來觀察硬碟中的快取資料大小)

15.5更新時間戳區(update timestamps)
程式碼示例:

15.6查詢快取
對於經常使用的查詢語句, 如果啟用了查詢快取, 當第一次執行查詢語句時, Hibernate 會把查詢結果存放在查詢快取中. 以後再次執行該查詢語句時, 只需從快取中獲得查詢結果, 從而提高查詢效能

查詢快取使用於如下場合:

 1.> 應用程式執行時經常使用查詢語句

 2.> 很少對與查詢語句檢索到的資料進行插入, 刪除和更新操作

l  查詢快取的特點

1、查詢快取基於二級快取的。

2、HQL的from Department的資料儲存在類快取區的,查詢快取區存放的是物件的ID

3、如果配置了查詢快取:將以SQL語句為key,查詢結果為value存放

 

 

l  查詢快取的使用步驟:

a、啟動查詢快取

hibernate.cfg.xml:

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

    b、程式中使用:query.setCacheable(true);

示例:

public void testQueryCache(){

              Session s1 = HibernateUtil.getCurrentSession();

              Transaction tx1 = s1.beginTransaction();

              Query query1 = s1.createQuery("from Employee where id<10");

              //啟用查詢快取,將整個list都儲存到查詢快取區,第一次查詢,

              //會從資料庫去查詢出這9條資料(為啥是9條呢?我資料庫裡id是從1開始的)

              query1.setCacheable(true);

              List<Employee> emps1 = query1.list();

              System.out.println(emps1.size());

              tx1.commit();

             

              Session s2 = HibernateUtil.getCurrentSession();

              Transaction tx2 = s2.beginTransaction();

              Query query2 = s2.createQuery("from Employee where id<9");

              //啟用查詢快取,取資料

              query2.setCacheable(true);

              //這時候就不會從資料庫裡去查詢了,但是如果第二次查詢的時候sql語句變化了,那麼這時候第二次取的時候

              //就又會再一次從資料庫裡去查詢了,因為查詢快取區維護的是一個map,key是sql語句,

              //值是該sql語句查詢出來的物件

              List<Employee> emps2 = query2.list();

              System.out.println(emps2.size());

              tx2.commit();

       }

 

l  Query介面的list VSiterate

 

list():只能放資料到二級快取,不能取,每次拿出來的資料是實體物件的所有的屬性

iterate():每次拿出來的集合資料是集合的ID屬性,當對集合中的資料進行遍歷的時候優先從二級快取中取每一個物件的資料,如果二級快取中存在則直接拿出來用,不存在,則到資料庫裡去取,有幾個物件不存在就會去查幾次,以下程式碼驗證了我們的結論:

 

public void testIterate(){

              Session s1 = HibernateUtil.getCurrentSession();

              Transaction tx1 = s1.beginTransaction();

              //先查詢出10條資料的所有欄位放到二級快取中

              List<Employee> emps1 = s1.createQuery("from Employee where id<10").list();

              System.out.println(emps1.size());

              tx1.commit();

             

              Session s2 = HibernateUtil.getCurrentSession();

              Transaction tx2 = s2.beginTransaction();

              //查詢出13條資料的所有id欄位,其他欄位不查詢

              Iterator<Employee> emps2 = s2.createQuery("from Employee where id < 13").iterate();

              //前面9條資料都會從二級快取中去取,所以不會產生sql語句,第10、11、12三條資料每次迭代都會從資料庫裡查一次

              while(emps2.hasNext())

                     System.out.println(emps2.next().getName());

              tx2.commit();

       }
---------------------
作者:zhoulenihao
來源:CSDN
原文:https://blog.csdn.net/zhoulenihao/article/details/25070575
版權宣告:本文為博主原創文章,轉載請附上博文連結!