前言
上一篇咱們介紹了 Hibernate 以及寫了一個 Hibernate 的工具類,快速入門體驗了一波 Hibernate 的使用,我們只需通過 Session 物件就能實作資料庫的操作了,
現在,這篇介紹使用 Hibernate 進行基本的 CRUD、懶加載以及快取的知識,
提示:如果你還沒看上一篇,那么建議你看完上一篇再來看這篇,
上一篇:一文快速入門體驗 Hibernate
基本的 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 的懶加載,有利于降低資料庫的開銷,當然快取也是,除了加快我們的訪問速度,也降低了直接訪問資料庫的開銷,快取就兩種,一級和二級,一級默認是開啟的,二級需要引入相關的依賴項,然后進行配置,開啟二級快取,配置快取策略,
這里附上整個專案的目錄結構,便于對照:
以上,就是本篇的內容,這些都應該掌握,咱們下期再見,
最后的最后
希望各位螢屏前的靚仔靚女們
給個三連!你輕輕地點了個贊,那將在我的心里世界增添一顆明亮而耀眼的星!
咱們下期再見!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552102.html
標籤:其他
上一篇:【Visual Leak Detector】核心原始碼剖析(VLD 2.5.1)
下一篇:返回列表