1. 程式人生 > 其它 >Hibernate【快取】知識要點

Hibernate【快取】知識要點

物件狀態

Hibernate中物件的狀態:

  • 臨時/瞬時狀態
  • 持久化狀態
  • 遊離狀態

學習Hibernate的物件狀態是為了更清晰地知道Hibernate的設計思想,以及是一級快取的基礎…當然啦,也就一點點知識

臨時/瞬時狀態

當我們直接new出來的物件就是臨時/瞬時狀態的..

  • 該物件還沒有被持久化【沒有儲存在資料庫中】
  • 不受Session的管理

這裡寫圖片描述

持久化狀態

當儲存在資料庫中的物件就是持久化狀態了

  • 當呼叫session的save/saveOrUpdate/get/load/list等方法的時候,物件就是持久化狀態
  • 在資料庫有對應的資料
  • 受Session的管理
  • 當對物件屬性進行更改的時候,會反映到資料庫中!

這裡寫圖片描述

我們來測試一下:當對物件屬性進行更改的時候,會反映到資料庫中!

        session.save(idCard);
        idCard.setIdCardName("我是測試持久化物件");

這裡寫圖片描述

遊離狀態

當Session關閉了以後,持久化的物件就變成了遊離狀態了…

  • 不處於session的管理
  • 資料庫中有對應的記錄

這裡寫圖片描述


一級快取

Hibernate有一級快取和二級快取之分,這裡主要講解一級快取

什麼是一級快取?

Hibenate中一級快取,也叫做session的快取,它可以在session範圍內減少資料庫的訪問次數! 只在session範圍有效! Session關閉,一級快取失效!

只要是持久化物件狀態的,都受Session管理,也就是說,都會在Session快取中!

Session的快取由hibernate維護,使用者不能操作快取內容; 如果想操作快取內容,必須通過hibernate提供的evit/clear方法操作

為什麼要是使用快取?

減少對資料庫的訪問次數!從而提升hibernate的執行效率!

測試

我們來看一下Hibernate是怎麼減少對資料庫訪問的次數的。

現在我的User表有這麼一條記錄:

這裡寫圖片描述

        //把資料放進cache
        User user = (User) session.get(User.class, 1);

        //發現要修改的欄位和cache一樣,不執行
        user.setUserName("你好2");

這裡寫圖片描述

這裡寫圖片描述

取資料也是一樣的

        User user = null;
        user = (User) session.get(User.class, 1);
        user = (User) session.get(User.class, 1);

這裡寫圖片描述


快取相關的方法

和快取有關常用的方法有三個:

  • session.flush(); 讓一級快取與資料庫同步
  • session.evict(arg0); 清空一級快取中指定的物件
  • session.clear(); 清空一級快取中快取的所有物件
  • clear
        User user = null;
        user = (User) session.get(User.class, 1);

        //清除快取,那麼下面獲取的時候,就不能從快取裡面拿了
        session.clear();
        user = (User) session.get(User.class, 1);
  • flush

在有快取的情況下,修改同一條記錄的資料,以最後的為準…因此只有一條update

        User user = null;
        user = (User) session.get(User.class, 1);

        user.setUserName("我是第一");
        user = (User) session.get(User.class, 1);
        user.setUserName("我是第二");

這裡寫圖片描述

我讓強制讓它和資料庫同步的話,就有兩條update了

        User user = null;
        user = (User) session.get(User.class, 1);

        user.setUserName("我是第一");
         session.flush();
        user = (User) session.get(User.class, 1);
        user.setUserName("我是第二");

這裡寫圖片描述

一般地,我們在批處理的時候會用,因為快取也是有大小的,如果1000條資料插入進去都要快取,那麼Hibernate可能就崩了

  • 每隔一定記錄數,先與資料庫同步 flush()
  • 再清空快取 clear()

值得注意的是:不同的Session是不會共享快取的!

Iterator與list

我們使用HQL查詢全部資料的時候,可以使用list()得到所有的資料,也可以使用iterator()得到一個迭代器,再遍歷迭代器…那它們有什麼區別呢?

。。。。當時看視訊的時候說是下圖:

這裡寫圖片描述

但是我在測試的時候:List也可以獲取快取的資料

這裡寫圖片描述

當然啦,Iterator也是可以獲取快取的資料

這裡寫圖片描述

因此,在獲取資料的時候還是使用list()方便!

懶載入

懶載入就是當使用資料的時候才去獲取資料、執行對應的SQL語句當還沒用到資料的時候,就不載入對應的資料!

主要目的就是為了提高Hibernate的效能,提高執行效率

  • get: 及時載入,只要呼叫get方法立刻向資料庫查詢
  • load:預設使用懶載入,當用到資料的時候才向資料庫查詢。

懶載入再次體驗

        User user = (User) session.load(User.class, 1);

        System.out.println("________");
        System.out.println(user);

這裡寫圖片描述

我們可以在對應的配置檔案用通常lazy屬性來設定

關閉懶載入:

    <class name="IdCard" table="IdCard" lazy="false">

這裡寫圖片描述

lazy有三個屬性:

  • true 使用懶載入
  • false 關閉懶載入
  • extra (在集合資料懶載入時候提升效率)【只有在set、list等集合標籤中使用】
    • 在真正使用資料的時候才向資料庫傳送查詢的sql;
    • 如果呼叫集合的size()/isEmpty()方法,只是統計,不真正查詢資料!

懶載入異常

當Session關閉後,就不能使用懶載入了,否則會報出異常

Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session

這裡寫圖片描述

報出了這個異常,我們有4種方法解決:

  • 方式1: 先使用一下資料
    • dept.getDeptName();
  • 方式2:強迫代理物件初始化
    • Hibernate.initialize(dept);
  • 方式3:關閉懶載入
    • 設定lazy=false;
  • **方式4: 在使用資料之後,再關閉session! **

Hibernate二級快取

前面我們已經講解過了一級快取,一級快取也就是Session快取,只在Session的範圍內有效…作用時間就在Session的作用域中,範圍比較小

Hibernate為我們提供了二級快取功能:二級快取是基於應用程式的快取,所有的Session都可以使用

  • Hibernate提供的二級快取有預設的實現,且是一種可插配的快取框架!如果使用者想用二級快取,只需要在hibernate.cfg.xml中配置即可; 不想用,直接移除,不影響程式碼。
  • 如果使用者覺得hibernate提供的框架框架不好用,自己可以換其他的快取框架或自己實現快取框架都可以

這裡寫圖片描述

Hibernate二級快取:儲存的是常用的類


配置二級快取

既然二級快取是Hibernate自帶的,那麼我們可以在hibernate.properties檔案中找到對應的資訊..

這裡寫圖片描述

  • #hibernate.cache.use_second_level_cache false【二級快取預設不開啟,需要手動開啟】
  • #hibernate.cache.use_query_cache true 【開啟查詢快取】
  • ## choose a cache implementation 【二級快取框架的實現】
  • #hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider
  • #hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider
  • hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider 預設實現
  • #hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider
  • #hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider
  • #hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider

通過配置檔案我們可以發現,二級快取預設是不開啟的,需要我們手動開啟,以下步驟:

  • 1)開啟二級快取
  • 2)指定快取框架
  • 3)指定哪些類加入二級快取

開啟二級快取

在hibernate.cfg.xml檔案中開啟二級快取

        <!-- a.  開啟二級快取 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>

指定快取框架

指定Hibernate自帶的二級快取框架就好了

        <!-- b. 指定使用哪一個快取框架(預設提供的) -->
        <property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>

指定哪些類加入二級快取

        <!-- c. 指定哪一些類,需要加入二級快取 -->
        <class-cache usage="read-write" class="zhongfucheng.aa.Monkey"/>
        <class-cache usage="read-only" class="zhongfucheng.aa.Cat"/>

測試:

我們知道一級快取是Session的快取,那麼我們在測試二級快取的時候使用兩個Session來測試就好了。如果第二個Session拿到的是快取資料,那麼就證明二級快取是有用的。

package zhongfucheng.aa;

import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;

public class App5 {
    public static void main(String[] args) {


        //獲取載入配置管理類
        Configuration configuration = new Configuration();
        //載入類對應的對映檔案!
        configuration.configure().addClass(Animal.class);
        //建立Session工廠物件
        SessionFactory factory = configuration.buildSessionFactory();
        //得到Session物件
        Session session1 = factory.openSession();
        //使用Hibernate操作資料庫,都要開啟事務,得到事務物件
        Transaction transaction = session1.getTransaction();
        //開啟事務
        transaction.begin();
        Monkey monkey = (Monkey) session1.get(Monkey.class,"40283f815be67f42015be67f43240001" );
        System.out.println(monkey.getName());
        System.out.println("-----------------------");



        Session session2 = factory.openSession();
        Transaction transaction2 = session2.getTransaction();
        transaction2.begin();
        Monkey monkey2 = (Monkey) session1.get(Monkey.class, "40283f815be67f42015be67f43240001");
        System.out.println(monkey2.getName());


        //提交事務
        transaction.commit();
        transaction2.commit();

        //關閉Session
        session1.close();
        session2.close();


    }
}

得到的是快取資料!

這裡寫圖片描述


快取策略

我們在把Animal類放進二級快取的時候,用法為只讀

這裡寫圖片描述

也就是說,只能讀取,不能寫入,我們來看看寫入會怎麼樣:

  monkey2.setName("小猴子");

丟擲了異常….

這裡寫圖片描述


usage的屬性有4種:

  • ** 放入二級快取的物件,只讀; **
  • 非嚴格的讀寫
  • 讀寫; 放入二級快取的物件可以讀、寫;
  • (基於事務的策略)

集合快取

如果我們在資料庫查詢的資料是集合…Hibernate預設是沒有為集合資料設定二級快取的…因此還是需要去讀寫資料庫的資訊

接下來,我們就看看把集合設定為二級快取是什麼做的:

  • 在hibernate.cgf.xml中配置物件中的集合為二級快取
        <!-- 集合快取[集合快取的元素物件,也加加入二級快取] -->
        <collection-cache usage="read-write" collection="cn.itcast.b_second_cache.Dept.emps"/>
  • 測試程式碼:
    public void testCache() {
        Session session1 = sf.openSession();
        session1.beginTransaction();
        // a. 查詢一次
        Dept dept = (Dept) session1.get(Dept.class, 10);
        dept.getEmps().size();// 集合
        session1.getTransaction().commit();
        session1.close();

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

        // 第二個session
        Session session2 = sf.openSession();
        session2.beginTransaction();
        // a. 查詢一次
        dept = (Dept) session2.get(Dept.class, 10);  // 二級快取配置好; 這裡不查詢資料庫
        dept.getEmps().size();

        session2.getTransaction().commit();
        session2.close();
    }

查詢快取

list()和iterator()會把資料放在一級快取,但一級快取只在Session的作用域中有效…如果想要跨Session來使用,就要設定查詢快取

我們在配置檔案中還看到了查詢快取這麼一條配置..

    #hibernate.cache.use_query_cache true      【開啟查詢快取】

也就是說,預設的查詢資料是不放在二級快取中的,如果我們想要把查詢出來的資料放到二級快取,就需要在配置檔案中開啟

        <!-- 開啟查詢快取 -->
        <property name="hibernate.cache.use_query_cache">true</property>
  • 在使用程式查詢的時候,也要呼叫setCacheable()方法,設定為查詢快取。
    @Test
    public void listCache() {
        Session session1 = sf.openSession();
        session1.beginTransaction();
        // HQL查詢  【setCacheable  指定從二級快取找,或者是放入二級快取】
        Query q = session1.createQuery("from Dept").setCacheable(true);
        System.out.println(q.list());
        session1.getTransaction().commit();
        session1.close();


        Session session2 = sf.openSession();
        session2.beginTransaction();
        q = session2.createQuery("from Dept").setCacheable(true);
        System.out.println(q.list());  // 不查詢資料庫: 需要開啟查詢快取
        session2.getTransaction().commit();
        session2.close();
    }