上一篇咱們介紹了 Hibernate 以及寫了一個 Hibernate 的工具類,快速入門體驗了一波 Hibernate 的使用,我們只需通過 Session 對象就能實現資料庫的操作了。 現在,這篇介紹使用 Hibernate 進行基本的 CRUD、懶載入以及緩存的知識。 ...
前言
上一篇咱們介紹了 Hibernate 以及寫了一個 Hibernate 的工具類,快速入門體驗了一波 Hibernate 的使用,我們只需通過 Session 對象就能實現資料庫的操作了。
現在,這篇介紹使用 Hibernate 進行基本的 CRUD、懶載入以及緩存的知識。
提示:如果你還沒看上一篇,那麼建議你看完上一篇再來看這篇。
基本的 CRUD
以下代碼均寫在測試類 HibernateTest 中
插入操作
這個在上一篇已經演示過,這裡便不再演示。
查詢操作
查詢有 2 種方式,通過 Session 對象的 get
方法 或者 load
方法來實現查詢,主要將查詢的數據結果封裝到一個 Java 對象中。
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
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=?
可以看到運行前和運行後,數據的變化,如圖:
如果屏幕前的小伙伴是按照我的步驟一步一步跟下來,那麼你可能會遇到中文亂碼的問題,此時需要在 hibernate.cfg.xml 配置文件中修改 URL,加上兩個參數
useUnicode=true&characterEncoding=UTF-8
,如下:
<property name="connection.url">jdbc:mysql://localhost:3306/demo_hibernate?useUnicode=true&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 個字的譯名:
- 瞬時態(Transient):當一個對象被實例化後,它處於瞬時態,簡單理解,就是
new 操作之後
。瞬時態的對象沒有與之關聯的資料庫記錄,並且沒有被 Hibernate 的 Session 管理。當將瞬時態的對象關聯到持久態對象或通過 Session 對象的save
、persist
等方法進行持久化操作後,該對象的狀態會發生變化,轉成持久態。 - 持久態(Persistent):當一個對象與 Hibernate 的 Session 關聯後,它就處於持久態。持久態的對象有與之對應的資料庫記錄,並且被 Hibernate 的 Session 管理。對持久態對象的任何更改都會自動同步到資料庫。持久態對象可以通過Session的
get
、load
等方法從資料庫中獲取,或者通過save
、update
、persist
等方法進行持久化操作。 - 游離態(Detached):當一個持久態對象與 Hibernate 的 Session 分離後,它處於游離態。游離態的對象仍然有與之對應的資料庫記錄,但不再受 Hibernate 的 Session 管理。對游離態對象的更改不會自動同步到資料庫。可以通過 Session 的
evict
、clear
等方法將持久態對象轉變為游離態對象,或者通過 Session 的merge
方法將游離態對象重新關聯到 Session 中。 - 刪除態(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。
- 引入依賴項
<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>
- 開啟二級緩存
二級緩存預設是關閉的,我們需要手動開啟。在 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>
- 創建緩存配置文件
我們在 /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>
- 在持久化類的映射文件中指定緩存策略
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 的懶載入,有利於降低資料庫的開銷,當然緩存也是,除了加快我們的訪問速度,也降低了直接訪問資料庫的開銷,緩存就兩種,一級和二級,一級預設是開啟的,二級需要引入相關的依賴項,然後進行配置,開啟二級緩存,配置緩存策略。
這裡附上整個項目的目錄結構,便於對照:
以上,就是本篇的內容,這些都應該掌握。咱們下期再見。
最後的最後
希望各位屏幕前的靚仔靚女們
給個三連!你輕輕地點了個贊,那將在我的心裡世界增添一顆明亮而耀眼的星!
咱們下期再見!