經典坑:從 Session 獲取的實體對象,僅在記憶體中 setXxx() 修改屬性,**無任何 save/update 呼叫**,資料庫卻自動執行了 UPDATE。本文深度剖析 Hibernate 三大狀態機制、髒檢查原理、脫管方案與最佳實踐。
// 1. 從資料庫加載實體 (進入持久態)
Company company = session.get(Company.class, 1L);
// 2. 構建新集合 (臨時態對象)
Set users = new HashSet<>();
for (int i=0; i<10; i++) {
User u = new User(); u.setUserName("wy"+i); users.add(u);
}
// 3. 僅 Set 集合屬性,未呼叫 save/update
company.setUsers(users);
// 4. 提交事務 / Session flush → 資料庫執行 UPDATE company ... / INSERT user ...
tx.commit(); // 或 session.flush()
現象:company 的關聯集合被持久化,新 User 被插入,關聯表更新。**無顯式持久化操作**。
flush() (提交事務前、查詢前、手動呼叫) 時,遍歷緩存中所有持久態對象,**比較當前屬性值與載入時快照值**。若不一致,自動生成 UPDATE SQL。@OneToMany(cascade = CascadeType.ALL) 或 cascade="all" 時,集合元素變更 (增/刪/改) 也納入髒檢查範圍,觸發關聯實體持久化/刪除/更新。graph LR
A[臨時態 Transient\nnew Entity()] -->|save()/persist()| B[持久態 Persistent\nSession 管理]
B -->|close()/evict()/clear()| C[遊離態 Detached\n無 Session 關聯]
C -->|update()/merge()/saveOrUpdate()| B
B -->|delete()/remove()| D[刪除態 Removed]
D -->|flush/commit| E[資料庫刪除]
C -->|序列化/反序列化| A
style B fill:#e1f5fe
style C fill:#fff3e0
狀態特徵對比:
| 特性 | 臨時態 | 持久態 | 遊離態 |
|---|---|---|---|
| Session 關聯 | 無 | 有 | 無 |
| 資料庫記錄 | 無 | 有 | 有 |
| 髒檢查 | 無 | **自動** | 無 |
| 修改屬性影響 DB | 否 | **是 (flush 時)** | 否 |
| 典型來源 | new Entity() | get/load/save/query | Session 關閉/evict |
需求:使用 company 對象 (需讀取其其他屬性),但**不希望** setUsers() 觸發資料庫更新。
Company company = session.get(Company.class, 1L);
// 關鍵:從 Session 緩存移除,變為遊離態
session.evict(company);
// 現在修改屬性/集合,不再觸發髒檢查
company.setUsers(newUsers);
// 若後續需持久化修改,需重新關聯
// session.update(company); 或 session.merge(company);
優點:精準、輕量、不影響 Session 中其他對象。
session.clear(); // 清空 Session 所有緩存,所有持久態→遊離態
缺點:影響範圍過大,可能導致同一事務中其他邏輯重複查詢、效能下降。
session.close(); // Session 關閉,所有對象自然遊離
// 但後續無法使用此 Session,需開新 Session
適用:業務邏輯結束、請求結束時。
Company company = session.get(Company.class, 1L);
// 不用原對象,新建一個只承載需修改的屬性
Company dto = new Company();
dto.setId(company.getId());
dto.setUsers(newUsers); // 臨時態,無髒檢查
// 後續用 session.update(dto) 或 merge(dto) 持久化
| 方法 | 適用狀態 | 行為 | 關鍵差異 |
|---|---|---|---|
| save() | 臨時態 | 持久化,生成 ID | 立即生成 ID (除 identity 策略) |
| persist() | 臨時態 | 持久化,JPA 標準 | ID 生成可延遲至 flush,不保證立即可用 |
| update() | 遊離態 | 重新關聯並標記髒 | Session 中已有相同 ID 對象會拋異常 |
| merge() | 遊離態/臨時態 | 拷貝狀態到持久態對象 | **不拋異常**,返回新持久態實例,原對象不變 |
| saveOrUpdate() | 臨時/遊離 | 智能判斷 save 或 update | Session 有同 ID 對象拋異常;無 ID→save,有 ID→update |
| lock() | 遊離態 (未修改) | 重新關聯為持久態 | 不檢查修改,**僅加鎖**,若對象已刪除會異常 |
| flush() | 持久態 | 同步髒數據到 DB | 不改變對象狀態,僅執行 SQL |
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = new User(); user.setName("wy"); // 臨時態
session.save(user); // 持久態
tx.commit(); // flush + 提交
session.close(); // 遊離態
// --- 另一個 Session / 請求 ---
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
// user 此處為遊離態
user.setName("wy2"); // 記憶體修改,無髒檢查
session2.update(user); // 重新關聯,標記髒
tx2.commit(); // flush → UPDATE user SET name='wy2'...
session2.close();
session.evict(entity) 精準解脫。@Transactional(flushMode=FlushMode.COMMIT) 延遲 flush 至提交,減少中間查詢觸發 flush。cascade=ALL 極易誤刪/誤更新,**建議明確聲明** cascade={PERSIST, MERGE} 或手動管理關聯。Hibernate 「Set 屬性自動更新 DB」非 Bug,乃**持久態髒檢查機制**使然。掌握三大狀態轉換、**evict() 精準解脫**、**merge() 安全合併**、**DTO 隔離持久態**,方能馭得 Hibernate 強大又危險的自動化持久化能力。