Hibernate 提供了豐富的持久化操作方法,但 save、persist、update、merge、saveOrUpdate、lock 之間的細微差異常讓開發者困惑。本文結合對象三態(瞬態/持久/脫管),從 SQL 語句、事務邊界、級聯行為、返回值等維度徹底釐清,並給出場景化選型建議。
save()/persist() update()/saveOrUpdate()/merge()/lock()
瞬態 ─────────────────────► 持久 ◄──────────────────────── 脫管
▲ │
│ delete() │
│ ▼
└────────────────────── 脫管 (evict/close/clear) ──────────┘
持久態修改 → 自動髒檢查 → flush/commit 時發送 UPDATE
new Entity(),無 ID,無關聯 Session| 方法 | 適用狀態 | SQL | ID 賦值時機 | 事務邊界外行為 | 返回值 | 級聯 | 核心語義 |
|---|---|---|---|---|---|---|---|
save() | 瞬態 | INSERT | 立即(需查詢生成器) | 立即執行 INSERT,不受事務控制 | 可序列化 ID | 支援 | 持久化新對象,立即獲取 ID |
persist() | 瞬態 | INSERT | flush/commit 時 | 不執行 INSERT,拋異常或延遲 | void | 支援 | JPA 標準,事務語義純粹 |
update() | 脫管 | UPDATE | - | - | void | 支援 | 重新關聯脫管對象,假設數據庫已存在 |
merge() | 脫管/瞬態 | INSERT/UPDATE | flush 時 | - | 持久態對象 (新實例) | 支援 | 智能合併:新則存,舊則更新,返回持久副本 |
saveOrUpdate() | 瞬態/脫管 | INSERT/UPDATE | 瞬態立即,脫管 flush | 瞬態立即 INSERT | void | 支援 | Hibernate 原生智能判斷 |
lock() | 脫管(未修改) | 無 (SELECT...FOR UPDATE) | - | - | void | 支援 | 重新關聯無修改脫管對象,獲取鎖 |
// 事務外調用
session.save(new User("A")); // 立即執行 INSERT,自動提交(若無事務)或加入隱式事務
session.persist(new User("B")); // 拋出 TransactionRequiredException 或延遲到事務開啟
// 長會話模式優勢
@Transactional
public void longConversation() {
User u = new User("C");
session.persist(u); // 不會立即 INSERT,僅標記為持久
// 業務邏輯...
// 若業務回滾,無垃圾數據;commit 時才真正 INSERT
}
結論:新項目優先用 persist() (JPA 標準);需立即獲取 ID 且無長會話場景可用 save()。
// update:原對象變為持久態
User detached = loadDetachedUser(); // 脫管對象
detached.setName("NewName");
session.update(detached); // detached 現在是持久態
// 後續修改 detached 會被髒檢查捕獲
// merge:返回新持久副本,原對象保持脫管
User detached2 = loadDetachedUser();
detached2.setName("NewName2");
User managed = (User) session.merge(detached2); // managed 是持久態
// detached2 仍是脫管態,managed 才是 Session 管理的實例
// 後續應操作 managed,而非 detached2
陷阱:merge() 返回的對象不是傳入對象,繼續操作傳入對象不會同步到數據庫!
// 內部邏輯簡化版
if (object 是持久態) return; // 無操作
if (存在同 ID 持久態對象) throw NonUniqueObjectException;
if (id 為 null 或 id 表示新對象) save(object);
else if (有版本字段且版本表示新對象) save(object);
else update(object);
// 脫管對象未修改,需重新關聯並獲取數據庫鎖
User detached = loadDetachedUser();
// 業務檢查...
session.lock(detached, LockMode.UPGRADE); // SELECT ... FOR UPDATE
// 現在 detached 變為持久態,可修改
detached.setBalance(detached.getBalance() - 100);
// flush 時發送 UPDATE
commit()、查詢前(自動 flush)、手動 flush()save() (Hibernate 原生) / persist() + flush() (JPA)persist()update()merge()saveOrUpdate() (Hibernate) / merge() (JPA)lock()persist() / merge(),避免 save()/update()/saveOrUpdate()| 錯誤代碼 | 問題 | 修正 |
|---|---|---|
session.save(detached) | 脫管對象有 ID,save 視為新對象 → 主鍵衝突 | 用 update() 或 merge() |
User u = session.merge(d); ... u.setX(); 但後續用 d 操作 | 操作脫管原對象,修改不入庫 | 後續統一操作 u (merge 返回值) |
session.saveOrUpdate(u) 同 Session 已有同 ID 對象 | NonUniqueObjectException | 先 evict() 舊對象,或用 merge() |
事務外 session.save() 導致數據不一致 | 無事務控制,部分成功部分失敗 | 包裝在 @Transactional / 手動事務中 |
總結:理解「對象三態」是掌握 Hibernate 保存方法的鑰匙。JPA 專案鎖定 persist()/merge() 雙核心;Hibernate 原生項目靈活運用 saveOrUpdate() 智能分發。牢記 merge() 返回副本、persist() 尊重事務邊界、lock() 專為無修改重新關聯設計,即可游刃有餘。