首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> JSP 程式設計 >> 關於JSP

Hibernate 數據保存操作原理深度剖析:save/update/saveOrUpdate/delete 內部流程與快取交互

     編輯:關於JSP

Session.save():從瞬時態到持久態的完整旅程

呼叫 session.save(entity) 時,Hibernate 內部執行以下步驟:

  1. 快取查重:在一級快取中尋找同 ID 實體,若存在直接返回(視為已持久化)。
  2. 生命週期回調:若實體實作 Lifecycle,呼叫 onSave()。
  3. 驗證:若實作 Validatable,呼叫 validate()。
  4. 攔截器:若存在 Interceptor,呼叫 onSave()。
  5. 構建 INSERT SQL:根據映射生成插入語句。
  6. 執行插入:發送 SQL,取得資料庫生成的 ID(如自增、Sequence)。
  7. 賦予 ID:將生成的 ID 設定回實體的 ID 屬性。
  8. 納入一級快取:將實體放入 Session 快取,標記為持久態。不放入二級快取(剛保存的物件極可能被修改,快取維護成本大於收益)。
  9. 級聯處理:若有 cascade="save-update" 關聯,遞歸處理關聯物件。

關鍵點:save() 並不立即發送 SQL,而是等待 flush() 或事務提交。若需立即取得 ID,可呼叫 flush()。

Session.update():游離態重新關聯與強制更新

// 典型跨 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();

內部流程:

  1. 快取查重:若快取已有同 ID 持久態實體,直接返回(不處理傳入物件)。
  2. 重新關聯:將游離物件納入一級快取,標記為持久態。
  3. 提交/flush 時:無條件執行 UPDATE SQL(預設),即使屬性未變更。

避免無異動 UPDATE:select-before-update

<class name="Customer" table="customer" select-before-update="true">
    ...
</class>

啟用後,flush 前先 SELECT 比對資料庫現值,僅有差異才發送 UPDATE。適用於「屬性變更頻率低」的實體;高頻變更實體建議關閉(預設),避免額外 SELECT 開銷。

兩大異常場景

  1. 資料庫無對應記錄:update() 拋 EntityNotFoundException。解決:改用 saveOrUpdate() 或 merge()。
  2. 快取已有同 ID 實體:拋 NonUniqueObjectException。解決:先 evict() 快取物件,或改用 merge()。

Session.saveOrUpdate():智慧分流新增與更新

核心邏輯:

  1. 快取查重:存在則返回。
  2. 判斷瞬時態條件(任一滿足即視為新物件):
    • ID 為 null(或未設定)。
    • 有 version 屬性且為 null。
    • ID 等於映射中 unsaved-value(預設 null)。
    • 版本等於 version 的 unsaved-value。
    • Interceptor.isUnsaved() 回傳 true。
  3. 瞬時態 → 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,否則無法正確判斷。

Session.delete():刪除持久態與游離態

// 刪除持久態
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

最佳實踐建議

  1. 同一 Session 內修改:零 API,依賴髒檢查。
  2. 跨請求/層傳遞物件:優先 merge(),安全且自動處理快取衝突。
  3. 批量新增:save() + 定期 flush() + clear(),控制記憶體。
  4. 批量更新/刪除:HQL executeUpdate(),繞過物件載入與快取。
  5. 樂觀鎖:所有實體加 <version>,update()/merge() 自動檢查版本,防止併發覆蓋。
  6. 避免 select-before-update 濫用:僅用於極低變更頻率實體。

理解每個操作對一級/二級快取的具體影響,才能在高併發、大數據量場景下寫出正確且高效的持久化代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved