动态

详情 返回 返回

Hibernate 基本操作、懶加載以及緩存 - 动态 详情

前言

上一篇咱們介紹了 Hibernate 以及寫了一個 Hibernate 的工具類,快速入門體驗了一波 Hibernate 的使用,我們只需通過 Session 對象就能實現數據庫的操作了。

現在,這篇介紹使用 Hibernate 進行基本的 CRUD、懶加載以及緩存的知識。

提示:如果你還沒看上一篇,那麼建議你看完上一篇再來看這篇。

上一篇:一文快速入門體驗 Hibernate

基本的 CRUD

以下代碼均寫在測試類 HibernateTest 中

插入操作

這個在上一篇已經演示過,這裏便不再演示。

查詢操作

查詢有 2 種方式,通過 Session 對象的 get 方法 或者 load 方法來實現查詢,主要將查詢的數據結果封裝到一個 Java 對象中。

  1. get 方法
    @Test
    public void queryByGet() {
        // 獲取 Session 對象
        Session session = HibernateUtil.getSession();
        try {
            // 使用 get() 方法,第一個參數是持久化類的類型參數,第二個參數是主鍵標識參數,如果沒有匹配的記錄,那麼會返回 null
            User user = session.get(User.class, new Integer("1"));
            System.out.println("用户ID:" + user.getId());
        } catch (Exception e) {
            System.out.println("查詢User數據失敗!");
            e.printStackTrace();
        } finally{
            // 關閉 Session 對象
            HibernateUtil.closeSession();
        }
    }

控制枱輸出:可以看到,執行了查詢 SQL,並打印了用户 ID。

INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 08, 2023 11:38:59 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户ID:1
  1. load 方法
    @Test
    public void queryByLoad() {
        // 獲取 Session 對象
        Session session = HibernateUtil.getSession();
        try {
            // 使用 load() 方法,它返回對象的代理,只有該代理被調用時,Hibernate 才會真正去執行 SQL 查詢
            User user = session.load(User.class, new Integer("1"));
            // ID 是已知的,不用進行查詢
            System.out.println("用户ID:" + user.getId());
            // 此時該代理被調用,就執行 SQL 語句,得到真正的數據記錄
            System.out.println("用户名稱:" + user.getName());
        } catch (Exception e) {
            System.out.println("查詢User數據失敗!");
            e.printStackTrace();
        } finally{
            // 關閉 Session 對象
            HibernateUtil.closeSession();
        }
    }

控制枱輸出:

五月 08, 2023 11:40:13 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 08, 2023 11:40:14 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
用户ID:1
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户名稱:god23bin

可以看到,是先打印用户ID的,這裏還沒有執行查詢 SQL,直到下一條語句中的 user.getName() 的執行,查詢的 SQL 語句才被 Hibernate 執行

修改操作

想對某條數據進行修改操作,那麼需要將它先查詢出來,然後進行修改。這裏就執行了兩條 SQL,保險起見,開啓事務,然後執行這兩條 SQL,接着提交事務。當然,這兩條 SQL,Hibernate 幫我們寫的啦!

    @Test
    public void update() {
        // 獲取 Session 對象
        Session session = HibernateUtil.getSession();
        try {
            // 開啓事務
            session.beginTransaction();
            // 進行查詢,將結果封裝成 user 對象
            User user = session.get(User.class, new Integer("1"));
            // 對 user 對象進行修改
            user.setName("公眾號:god23bin");
            user.setPassword("456789");
            // 提交事務
            session.getTransaction().commit();
        } catch (Exception e) {
            // 發生異常,則回滾事務
            session.getTransaction().rollback();
            System.out.println("修改User數據失敗!");
            e.printStackTrace();
        } finally{
            // 關閉 Session 對象
            HibernateUtil.closeSession();
        }
    }

控制枱輸出:

五月 09, 2023 12:00:16 上午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 09, 2023 12:00:17 上午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    update
        user 
    set
        name=?,
        password=? 
    where
        id=?
        

可以看到運行前和運行後,數據的變化,如圖:

image-20230509000227733

如果屏幕前的小夥伴是按照我的步驟一步一步跟下來,那麼你可能會遇到中文亂碼的問題,此時需要在 hibernate.cfg.xml 配置文件中修改 URL,加上兩個參數 useUnicode=true&characterEncoding=UTF-8,如下:

<property name="connection.url">jdbc:mysql://localhost:3306/demo_hibernate?useUnicode=true&amp;characterEncoding=UTF-8</property>

刪除操作

刪除操作需要先把數據查詢出來,然後通過 Session 對象的 delete 方法將其刪除。代碼如下:

    @Test
    public void delete() {
        // 獲取 Session 對象
        Session session = HibernateUtil.getSession();
        try {
            session.beginTransaction();
            User user = session.get(User.class, new Integer("1"));
            // 刪除操作
            session.delete(user);
            session.getTransaction().commit();
        } catch (Exception e) {
            session.getTransaction().rollback();
            System.out.println("刪除User數據失敗!");
            e.printStackTrace();
        } finally{
            // 關閉 Session 對象
            HibernateUtil.closeSession();
        }
    }

控制枱輸出:

五月 09, 2023 12:10:09 上午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 09, 2023 12:10:10 上午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
Hibernate: 
    delete 
    from
        user 
    where
        id=?

關於 Hibernate 中對象的狀態

在Hibernate中,對象的狀態有 4 種,分別為 Transient、Persistent、Detached、Removed,譯名就比較多,方便起見,我選擇 3 個字的譯名:

  1. 瞬時態(Transient):當一個對象被實例化後,它處於瞬時態,簡單理解,就是 new 操作之後。瞬時態的對象沒有與之關聯的數據庫記錄,並且沒有被 Hibernate 的 Session 管理。當將瞬時態的對象關聯到持久態對象或通過 Session 對象的 savepersist 等方法進行持久化操作後,該對象的狀態會發生變化,轉成持久態。
  2. 持久態(Persistent):當一個對象與 Hibernate 的 Session 關聯後,它就處於持久態。持久態的對象有與之對應的數據庫記錄,並且被 Hibernate 的 Session 管理。對持久態對象的任何更改都會自動同步到數據庫。持久態對象可以通過Session的 getload 等方法從數據庫中獲取,或者通過 saveupdatepersist 等方法進行持久化操作。
  3. 遊離態(Detached):當一個持久態對象與 Hibernate 的 Session 分離後,它處於遊離態。遊離態的對象仍然有與之對應的數據庫記錄,但不再受 Hibernate 的 Session 管理。對遊離態對象的更改不會自動同步到數據庫。可以通過 Session 的 evictclear 等方法將持久態對象轉變為遊離態對象,或者通過 Session 的 merge 方法將遊離態對象重新關聯到 Session 中。
  4. 刪除態(Removed):當一個持久態對象被從 Hibernate 的 Session中刪除後,它處於刪除態。刪除態的對象仍然有與之對應的數據庫記錄,但即將被從數據庫中刪除。刪除態對象可以通過 Session 的delete 方法進行刪除操作。

Hibernate 通過跟蹤對象的狀態變化,實現了對象與數據庫的同步。在 Hibernate 的事務管理中,對象的狀態轉換是自動進行的,我們無需手動操作,Hibernate 會根據對象的狀態進行相應的數據庫操作,保證對象與數據庫的一致性。

需要注意的是,Hibernate 的對象狀態與數據庫的操作並不是一一對應的,Hibernate 提供了一系列的持久化方法和操作,我們可以根據具體的需求選擇合適的方法來進行對象狀態的轉換和數據庫操作。對於複雜的業務邏輯和數據處理,需要仔細理解和管理對象的狀態,以避免數據不一致的問題。

懶加載

Hibernate 的懶加載(Lazy Loading)是一種延遲加載策略,它允許程序在需要訪問相關數據時才從數據庫中加載關聯對象的屬性或集合。

在 Hibernate 中,懶加載是通過使用代理對象來實現的。實際上,我們在演示 Session 對象的 load() 方法時,就是懶加載了,一開始返回的是代理對象,並沒有直接查詢數據庫,而是直到該代理對象的屬性或方法被調用時,Hibernate 會根據需要自動執行額外的數據庫查詢,從而延遲加載關聯的數據。

這就是懶加載,等到需要的時候才去加載。

懶加載的主要優點是可以提高系統性能和減少不必要的數據庫查詢。如果一個對象關聯的屬性或集合在業務邏輯中很少被使用,懶加載可以避免不必要的數據庫訪問,減輕數據庫負載。

除了 load 方法實現的懶加載,我們還可以通過設置映射文件中的 <property> 標籤的 lazy 屬性實現懶加載:

<property name="name" type="string" lazy="true" /> <!-- name 屬性被設置成懶加載-->

緩存

緩存是一種臨時存儲數據的方式,將數據保存在更快速的存儲介質(如內存)中,以便將來能夠快速訪問和檢索。

Hibernate 提供了緩存的技術,主要用於存儲實體對象以及查詢的結果集。緩存分為一級緩存(Session 緩存)和二級緩存(Session Factory 緩存)

一級緩存

一級緩存是與 Session 相關聯的緩存,它存儲了從數據庫中讀取的實體對象。在同一個 Session 中,當多次查詢相同的數據時,Session 首先會根據對應的持久化類和唯一性標識(一般指的是ID)去緩存中查找是否存在該數據。如果存在,則直接從緩存中獲取,而不再訪問數據庫;如果不存在,則繼續向二級緩存種查找。

一級緩存是默認開啓的,可以提高讀取性能。

示例:

    @Test
    public void testFirstLevelCache() {
        Session session = HibernateUtil.getSession();
        try {
            System.out.println("第一次查詢:");
            User user = session.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user.getName());
            
            System.out.println("第二次查詢:");
            User user2 = session.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user2.getName());
        } catch (Exception e) {
            System.out.println("查詢User數據失敗!");
            e.printStackTrace();
        } finally{
            // 關閉 Session 對象
            HibernateUtil.closeSession();
        }
    }

控制枱輸出:

五月 09, 2023 9:35:31 下午 org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
五月 09, 2023 9:35:32 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
第一次查詢:
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户名:god23bin
第二次查詢:
用户名:god23bin

可以看到,第二次查詢是沒有執行 SQL 的,直接從一級緩存中獲取。

二級緩存

二級緩存是在 SessionFactory 級別上的緩存,用於緩存多個 Session 之間共享的數據。它可以減少對數據庫的訪問次數,提高性能和擴展性。二級緩存可以存儲實體對象、集合對象以及查詢結果集。

由於 Hibernate 本身並未提供二級緩存的具體實現,所以需要藉助其他緩存插件或者説策略來實現二級緩存。比如 Ehcache、Redis 等。

我們這裏直接使用 Ehcache。

  1. 引入依賴項
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>5.6.14.Final</version>
</dependency>

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.0</version>
</dependency>
  1. 開啓二級緩存

二級緩存默認是關閉的,我們需要手動開啓。在 hibernate.cfg.xml 中開啓二級緩存:

<?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        ...
        <!-- 開啓二級緩存 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- 開啓查詢緩存 -->
        <property name="hibernate.cache.use_query_cache">true</property>
        <!-- 指定使用的緩存實現類 -->
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.SingletonEhcacheRegionFactory</property>
    </session-factory>
</hibernate-configuration>
  1. 創建緩存配置文件

我們在 /src/main/resources 目錄下創建緩存配置文件 ehcache.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!-- 硬盤存儲:將緩存中暫時不使用的對象,持久化到硬盤 -->
    <!-- path 屬性:指定在硬盤上存儲對象的路徑 -->
    <!-- java.io.tmpdir 是默認的臨時文件路徑。 可以通過右邊的方式打印出具體的文件路徑: System.out.println(System.getProperty("java.io.tmpdir")); -->
    <diskStore path="java.io.tmpdir"/>


    <!-- defaultCache:默認的緩存策略 -->
    <!-- eternal 屬性:設定緩存的elements是否永遠不過期。如果為true,則緩存的數據始終有效,如果為false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷 -->
    <!-- maxElementsInMemory 屬性:在內存中緩存的element的最大數目 -->
    <!-- overflowToDisk 屬性:如果內存中數據超過內存限制,是否要緩存到硬盤上 -->
    <!-- diskPersistent 屬性:是否在硬盤上持久化。指重啓 JVM 後,數據是否有效。默認為false -->
    <!-- timeToIdleSeconds 屬性:對象空閒時間(單位:秒),指對象在多長時間沒有被訪問就會失效。只對eternal為false的有效。默認值0,表示一直可以訪問 -->
    <!-- timeToLiveSeconds 屬性:對象存活時間(單位:秒),指對象從創建到失效所需要的時間。只對eternal為false的有效。默認值0,表示一直可以訪問 -->
    <!-- memoryStoreEvictionPolicy 屬性:緩存的 3 種清除策略,因為緩存區域是一定的,滿了之後就需要清除不需要的數據 -->
    <!-- 1. FIFO:first in first out (先進先出). 先緩存的數據會先被清除-->
    <!-- 2. LFU:Less Frequently Used (最少使用).意思是一直以來最少被使用的。緩存的元素有一個 hit 屬性,hit 值最小的將會被清除 -->
    <!-- 3. LRU:Least Recently Used(最近最少使用). (ehcache 默認值).緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清除 -->

    <defaultCache eternal="false"
                  maxElementsInMemory="1000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="0"
                  timeToLiveSeconds="600"
                  memoryStoreEvictionPolicy="LRU"/>

    <!-- name: Cache的名稱,必須是唯一的(ehcache會把這個cache放到HashMap裏)-->
    <cache name="userCache"
           eternal="false"
           maxElementsInMemory="100"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="0"
           timeToLiveSeconds="300"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>
  1. 在持久化類的映射文件中指定緩存策略

User.hbm.xml:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="cn.god23bin.demo.domain.entity.User" table="user">
        <!-- 指定緩存策略 -->
        <cache usage="read-only"/>
        ...
    </class>
</hibernate-mapping>

測試二級緩存:

    @Test
    public void testSecondLevelCache() {
        Session session1 = HibernateUtil.getSession();
        Session session2 = HibernateUtil.getSession();
        try {
            System.out.println("第一個 Session 去查詢數據並封裝成對象");
            User user1 = session1.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user1.getName());

            System.out.println("第二個 Session 去查詢同一數據並封裝成對象");
            User user2 = session2.get(User.class, new Integer("2"));
            System.out.println("用户名:" + user1.getName());
        } catch (Exception e) {
            System.out.println("查詢User數據失敗!");
            e.printStackTrace();
        } finally{
            // 關閉 Session 對象
            HibernateUtil.closeSession();
        }
    }

控制枱輸出:

五月 09, 2023 11:18:31 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
第一個 Session 去查詢數據並封裝成對象
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.name as name2_0_0_,
        user0_.password as password3_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
用户名:god23bin
第二個 Session 去查詢同一數據並封裝成對象
用户名:god23bin

總結

本篇文章主要講了基本的 CRUD 操作,都是通過 Session 去操作的,根據一個持久化類的類型以及一個唯一標識進行相關操作,然後講了 Hibernate 中的對象的狀態,有 4 種,分別是瞬時、持久、遊離、刪除。

接着説了 Hibernate 的懶加載,有利於降低數據庫的開銷,當然緩存也是,除了加快我們的訪問速度,也降低了直接訪問數據庫的開銷,緩存就兩種,一級和二級,一級默認是開啓的,二級需要引入相關的依賴項,然後進行配置,開啓二級緩存,配置緩存策略。

這裏附上整個項目的目錄結構,便於對照:

image-20230509234027184

以上,就是本篇的內容,這些都應該掌握。咱們下期再見。

最後的最後

希望各位屏幕前的靚仔靚女們給個三連!你輕輕地點了個贊,那將在我的心裏世界增添一顆明亮而耀眼的星!

咱們下期再見!

user avatar u_16297326 头像 journey_64224c9377fd5 头像 xuxueli 头像 AmbitionGarden 头像 lenglingx 头像 buildyuan 头像 aipaobudeshoutao 头像 lenve 头像 aipaobudezuoyeben 头像 immerse 头像 chenjiabing666 头像 shouke 头像
点赞 46 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.