多用戶並發訪問同一數據時,必須採取並發控制機制。Hibernate 3 提供悲觀鎖 (資料庫層面) 與樂觀鎖 (應用層面版本號) 兩大策略,並支援 Lifecycle、Validatable 介面實現領域模型自我驗證。本文系統講解並附完整代碼示例。
假設並發衝突頻繁,在讀取/更新時獨佔鎖定記錄直到事務結束。通過 LockMode 指定:
LockMode.UPGRADE:SELECT ... FOR UPDATE (標準 SQL)。LockMode.UPGRADE_NOWAIT:Oracle FOR UPDATE NOWAIT,不等待直接報錯。LockMode.READ、WRITE、NONE:Hibernate 內部自動管理 (讀/寫/無鎖)。// Query/Criteria 級別
Query q = session.createQuery("from User u");
q.setLockMode("u", LockMode.UPGRADE);
List users = q.list();
// 單一實體載入/鎖定
User u = (User) session.load(User.class, id, LockMode.UPGRADE);
// 或已載入實體後升級鎖
session.lock(u, LockMode.UPGRADE);
生成 SQL:select ... from user u for update。若資料庫不支援指定模式,Hibernate 自動降級而非拋異常。
假設衝突稀少,讀取時攜帶版本號,更新時比對版本號:
StaleObjectStateException。version INT。private Integer version; 含 getter/setter。<class name="User" table="user" optimistic-lock="version">
<id .../>
<version name="version" column="version" type="java.lang.Integer"/> <!-- 必須在 <id> 之後 -->
<property .../>
</class>
// 用戶 1 載入
Session s1 = sf.openSession(); User u1 = (User) s1.load(User.class, 1);
// 用戶 2 載入同一記錄
Session s2 = sf.openSession(); User u2 = (User) s2.load(User.class, 1);
// 版本號相同 (如 0)
Transaction tx1 = s1.beginTransaction();
u1.setAge(31); tx1.commit(); // 版本號變 1
Transaction tx2 = s2.beginTransaction();
u2.setName("Justin"); // u2 版本號仍 0
tx2.commit(); // 拋出 StaleObjectStateException
異常處理策略:捕獲異常 → 重新載入最新數據 → 合併用戶修改 → 重試提交,或呈現衝突讓用戶決定。
樂觀鎖屬應用層邏輯,若繞過 Hibernate 直接更新資料庫 (如批次腳本、外部系統) 未同步版本號,鎖定失效。嚴禁手動修改 version 屬性繞過檢查。
實體實作 org.hibernate.classic.Lifecycle,Session 操作前自動回調:
public interface Lifecycle {
boolean onSave(Session s); // true/異常 → 取消儲存
boolean onUpdate(Session s); // true/異常 → 取消更新
boolean onDelete(Session s); // true/異常 → 取消刪除
void onLoad(Session s, Serializable id);
}
適用場景:儲存前自動填充衍生欄位、更新時記錄變更日誌、刪除前檢查引用完整性。
實體實作 org.hibernate.classic.Validatable:
public interface Validatable {
void validate() throws ValidationFailure;
}
持久化前 (save/update) 自動調用 validate(),拋出 ValidationFailure 則取消操作。適合領域模型內嵌業務規則驗證 (如金額>0、日期邏輯)。
| 機制 | 層級 | 適用場景 | 注意事項 |
|---|---|---|---|
| 悲觀鎖 | 資料庫 | 高衝突、短事務、需強一致性 | 長事務易死鎖、擴展性差 |
| 樂觀鎖 | 應用 (版本號) | 低衝突、長會話、Web 應用首選 | 需防繞過、外部更新同步版本號 |
| Lifecycle | 實體回調 | 審計、衍生欄位、軟刪除 | 耦合 Hibernate API、慎用 |
| Validatable | 實體驗證 | 領域不變約束 | 異常類型固定、建議改用 Bean Validation (JSR 303) |
現代開發建議:並發控制首選樂觀鎖 (配合 @Version 註解);業務驗證遷移至 Bean Validation;審計等橫切關注點採用 AOP 或事件監聽器 (JPA @EntityListeners),降低領域模型對持久化框架耦合。