呼叫 session.save(entity) 時,Hibernate 內部執行以下步驟:
Lifecycle,呼叫 onSave()。Validatable,呼叫 validate()。Interceptor,呼叫 onSave()。cascade="save-update" 關聯,遞歸處理關聯物件。關鍵點:save() 並不立即發送 SQL,而是等待 flush() 或事務提交。若需立即取得 ID,可呼叫 flush()。
// 典型跨 Session 更新場景
Session s1 = sf.openSession(); tx1 = s1.beginTransaction();
Customer c = s1.get(Customer.class, 1L); // 持久態
tx1.commit(); s1.close(); // c 變為游離態
c.setName("New Name"); // 在 Session 外修改
Session s2 = sf.openSession(); tx2 = s2.beginTransaction();
s2.update(c); // 重新關聯,標記為需更新
tx2.commit(); s2.close();
內部流程:
select-before-update<class name="Customer" table="customer" select-before-update="true">
...
</class>
啟用後,flush 前先 SELECT 比對資料庫現值,僅有差異才發送 UPDATE。適用於「屬性變更頻率低」的實體;高頻變更實體建議關閉(預設),避免額外 SELECT 開銷。
update() 拋 EntityNotFoundException。解決:改用 saveOrUpdate() 或 merge()。NonUniqueObjectException。解決:先 evict() 快取物件,或改用 merge()。核心邏輯:
null(或未設定)。version 屬性且為 null。unsaved-value(預設 null)。version 的 unsaved-value。Interceptor.isUnsaved() 回傳 true。save();游離態 → update()。映射配置關鍵:
<id name="id" column="id" type="long" unsaved-value="0"> <!-- ID=0 視為新物件 -->
<generator class="native"/>
</id>
<version name="version" column="version" unsaved-value="0"/> <!-- 版本=0 視為新物件 -->
若 ID 生成策略為 assigned(手動指定),必須設定 unsaved-value 或實作 Interceptor,否則無法正確判斷。
// 刪除持久態
Customer c = session.load(Customer.class, 1L);
session.delete(c); // 標記為待刪除,flush 時執行 DELETE
// 刪除游離態
Customer detached = ...;
session.delete(detached); // 先重新關聯,再標記刪除
// HQL 批量刪除(不載入物件,單條 SQL)
session.createQuery("delete Customer c where c.age > 0").executeUpdate();
注意:delete() 僅從資料庫刪除記錄,Java 物件在記憶體中仍存在,但不再受 Hibernate 管理。
| 操作 | 一級快取影響 | 二級快取影響 | SQL 觸發時機 |
|---|---|---|---|
save() | 納入 | 不納入 | flush/commit |
update() | 納入(重新關聯) | 更新(若啟用) | flush/commit |
saveOrUpdate() | 納入 | 更新/納入 | flush/commit |
merge() | 納入(回傳新實例) | 更新 | flush/commit |
delete() | 移除(flush 時) | 失效/移除 | flush/commit |
flush() | 同步髒資料 | 同步 | 立即 |
evict() | 移除指定 | 不影響 | 無 |
clear() | 清空所有 | 不影響 | 無 |
refresh() | 用 DB 覆蓋 | 不影響 | 立即 SELECT |
merge(),安全且自動處理快取衝突。save() + 定期 flush() + clear(),控制記憶體。executeUpdate(),繞過物件載入與快取。<version>,update()/merge() 自動檢查版本,防止併發覆蓋。select-before-update 濫用:僅用於極低變更頻率實體。理解每個操作對一級/二級快取的具體影響,才能在高併發、大數據量場景下寫出正確且高效的持久化代碼。