在 Hibernate 映射文件中配置 dynamic-update="true",本意是希望執行 UPDATE 時僅更新**已修改的屬性** (Dirty Checking),減少數據庫鎖競爭、Binlog 體積與網絡傳輸。然而許多開發者發現:明明只設了一個字段,SQL 卻更新了所有列。本文通過實測代碼揭示根因:**dynamic-update 僅對持久化對象生效,對臨時對象無效**,並給出正確使用模式。
// News.java
public class News {
private Integer id;
private String title, author;
private Date date;
// getter/setter/toString 省略
}
// News.hbm.xml
<class name="com.jdw.hibernate.entities.News" table="NEWS"
lazy="false" select-before-update="true" dynamic-update="true">
<id name="id" type="java.lang.Integer" unsaved-value="110">
<column name="ID" />
<generator class="native" />
</id>
<property name="title" ><column name="TITLE" /></property>
<property name="author" ><column name="AUTHOR" /></property>
<property name="date" ><column name="DATE" /></property>
</class>
@Test
public void testDynamicUpdate() {
News news = new News(); // 臨時對象
news.setAuthor("test"); // 僅設 author
news.setId(13); // 手動設 ID
session.update(news); // 預期只更新 AUTHOR
}
Hibernate: select news_.ID, news_.TITLE as TITLE2_1_, news_.AUTHOR as AUTHOR3_1_, news_.DATE as DATE4_1_ from NEWS news_ where news_.ID=? Hibernate: update NEWS set TITLE=?, AUTHOR=?, DATE=? where ID=?
結果:TITLE、DATE 被更新為 NULL,**數據損壞風險極大**。
Hibernate 區分三種對象狀態:
dynamic-update 依賴 **Dirty Checking 機制**:Session 緩存中保存實體的**原始快照**,Flush 時對比當前值與快照值,僅生成差異列。臨時對象無快照,Hibernate 無法判斷哪些屬性「變了」,只能退化為全列更新 (或按 select-before-update 先查再全更新)。
@Test
public void testDynamicUpdateCorrect() {
News news = (News) session.get(News.class, 13); // 變為持久態,Session 有快照
news.setAuthor("test"); // 僅修改 author
// 無需顯式 update,事務提交時自動 Flush → 僅更新 AUTHOR 列
}
Hibernate: update NEWS set AUTHOR=? where ID=?
| 場景 | 做法 | dynamic-update 效果 |
|---|---|---|
| 業務邏輯中修改實體 | 獲取持久對象 → 修改屬性 → 事務提交 | ✅ 生效,僅更新髒字段 |
| DTO 轉 Entity 更新 | 先 session.get() 加載 → 拷貝非空屬性 (MapStruct/BeanUtils) → 提交 | ✅ 生效 |
| 批量更新 | HQL/JPQL update News set author=? where id=? 或 @Modifying | ⚠️ 繞過實體機制,直接生成 SQL |
| 游離對象合併 | session.merge(detachedObj) → 返回持久對象 → 修改 → 提交 | ✅ 生效 (merge 後變持久態) |
| 新建對象帶 ID update | 嚴禁,會導致全列更新或主鍵衝突 | ❌ 無效 |
@DynamicUpdate (JPA 註解) / dynamic-update="true" (XML) — 類級別開啟。@SelectBeforeUpdate / select-before-update="true" — 配合使用:若版本號/時間戳未變則跳過 UPDATE,**但不解決臨時對象問題**。@OptimisticLocking(type = OptimisticLockType.DIRTY) — 基於髒字段樂觀鎖,進一步減少衝突。@Basic(fetch=LAZY) + @LazyToOne),避免動態更新時誤觸發大字段讀寫。dynamic-update 是 Hibernate 為「領域模型驅動開發」設計的優化開關,**前提是你必須在持久態下操作實體**。若習慣「新建 Entity → set ID → update」的 Anemic Domain Model 寫法,請直接放棄此配置,改用顯式 HQL/JPQL 或 MyBatis 精確控制 SQL,否則將引入難以排查的數據完整性 Bug。