Hibernate 進階核心:實體狀態流轉、一級緩存/快照機制、一對多/多對多映射實戰
本文系統講解 Hibernate 3 核心機制:持久化實體三種狀態(瞬時/持久/脫管)與轉換規則、Session 一級緩存與快照自動更新原理、get/load 差異、級聯與外鍵維護配置、一對多與多對多雙向關聯映射最佳實踐。
一、持久化實體三態與轉換
| 狀態 | 特徵 | OID | Session 關聯 | 資料庫記錄 |
| 瞬時態 | new 建立,未關聯 Session | 無 | 否 | 無 |
| 持久態 | 受 Session 管理,在緩存中 | 有 | 是 | 有(提交前可能無) |
| 脫管態 | Session 關閉/清除後 | 有 | 否 | 有 |
狀態轉換關鍵方法
- 瞬時 → 持久:
save()、saveOrUpdate()、persist()
- 持久 → 脫管:
close()、evict()、clear()
- 脫管 → 持久:
update()、saveOrUpdate()、lock()、merge()
- 持久 → 瞬時:
delete()(資料庫記錄刪除)
- 脫管 → 瞬時:設
id=null 或手動刪除資料庫記錄
核心原則:Hibernate 僅識別 OID。同一 OID 在同一 Session 內只能存在一個持久實例。
二、Session 一級緩存(Session Cache)
本質
Session 內部維護 Map<EntityKey, Object> 等集合,**生命週期 = Session 生命週期**。所有持久態物件自動放入緩存。
作用驗證
// 同一 Session 多次 get 同一 OID
Customer c1 = session.get(Customer.class, 1); // 發 SQL
Customer c2 = session.get(Customer.class, 1); // 無 SQL,直接返回緩存物件
assert c1 == c2; // 同一實例
跨 Session 無效:Session 關閉,緩存銷毀;新 Session 必須重查資料庫。
三、快照機制與自動更新
概念
快照 = 物件載入時的狀態副本(資料庫映射)。**緩存物件被修改 → 緩存 ≠ 快照 → flush 時自動發 UPDATE 同步資料庫**。
flush 觸發時機
transaction.commit()(先 flush 再 commit)
- 手動
session.flush()
- 執行 HQL/QL/Criteria 查詢(
get/load 除外):為保證查詢結果反映最新狀態,先 flush 再查。
get/load 特殊行為
- 優先走緩存,即使緩存已修改(≠ 快照),也不 flush,直接返回緩存物件**。
get:立即查詢,無記錄返回 null。
load:延遲載入(代理物件),僅存取 id 不查詢;存取其他屬性才查詢。無記錄拋 ObjectNotFoundException。
緩存操作 API
| 方法 | 作用 | 狀態變化 |
| flush() | 同步緩存→資料庫 | 持久態維持 |
| clear() | 清空整個緩存 | 所有持久→脫管 |
| evict(obj) | 清除指定物件 | 該物件持久→脫管 |
| refresh(obj) | 資料庫覆蓋緩存 | 持久態維持,修改丟失 |
四、一對多雙向關聯映射(Customer ↔ Order)
表結構
t_customer (id PK, name)
t_order (id PK, name, cid FK → t_customer.id)
映射檔關鍵配置
<!-- Order.hbm.xml 多方 -->
<many-to-one name="customer" class="Customer" column="cid"/>
<!-- Customer.hbm.xml 一方 -->
<set name="orders" inverse="true" cascade="save-update,delete">
<key column="cid"/>
<one-to-many class="Order"/>
</set>
關鍵屬性解析
- inverse="true":一方放棄外鍵維護權,**由多方(Order)負責維護 cid**,避免雙方各發一條 UPDATE(多餘 SQL)。**生產環境強烈建議一方設 true**。
- cascade="save-update,delete":一方操作級聯到多方。
save-update 允許持久態關聯瞬時/脫管物件自動 save/update;delete 刪除一方時級聯刪除多方(**被刪物件必須是持久態**)。
- 雙向建立關聯**:
customer.getOrders().add(order); order.setCustomer(customer); 兩邊皆需設定,否則記憶體模型不一致。
級聯刪除陷阱
// 錯誤:脫管態刪除,級聯失效,僅置空外鍵
Customer c = new Customer(); c.setId(1);
session.delete(c);
// 正確:持久態刪除,級聯生效
Customer c = session.get(Customer.class, 1);
session.delete(c); // 訂單同步刪除
五、多對多雙向關聯映射(Student ↔ Course)
表結構
t_student (id PK, name)
t_course (id PK, name)
t_s_c (sid FK, cid FK, 聯合 PK)
映射檔關鍵配置
<!-- Student.hbm.xml -->
<set name="courses" table="t_s_c" inverse="true" cascade="save-update">
<key column="sid"/>
<many-to-many class="Course" column="cid"/>
</set>
<!-- Course.hbm.xml -->
<set name="students" table="t_s_c">
<key column="cid"/>
<many-to-many class="Student" column="sid"/>
</set>
最佳實踐
- 不建議配置 delete 級聯**:刪除學生不應刪除課程,否則雙向級聯會清空關聯表甚至級聯刪除另一方實體,造成數據災難。
- 一方設 inverse="true":由另一方維護中間表,避免重複 INSERT/DELETE。
- 建立關聯只需單邊操作**:
student.getCourses().add(course); 即可(配合 cascade 的一方)。
- 解除關聯**:
student.getCourses().remove(course); → flush 時自動 DELETE 中間表記錄。
- 修改關聯**:Hibernate 無法對中間表 UPDATE,**只能先 remove 再 add**(先解除舊關聯再建立新關聯)。
六、範式設計與建表規範回顧
- 1NF:欄位原子性
- 2NF:有主鍵,非主鍵完全依賴主鍵
- 3NF:非主鍵不傳遞依賴(僅依賴主鍵)
- 一對多:多方表加外鍵
- 多對多:產生關聯表,雙外鍵聯合主鍵
掌握三態轉換、緩存/快照機制、inverse/cascade 語義,是駕馭 Hibernate 高效開發、避免效能與數據一致性陷阱的關鍵。
- 上一篇文章: Spring MVC @CookieValue 註解快速上手:直接綁定 Cookie 值
- 下一篇文章: Java 入門第二季:深入理解類與對象的核心概念
Copyright ©
程式碼之家 All Rights Reserved