Hibernate 的事務與併發模型直接映射到底層 JDBC/JTA,不添加額外鎖定行為,核心僅提供自動版本控制(樂觀鎖)。理解 Session 生命週期、事務邊界劃分、工作單元模式,是構建高併發、可擴展應用的基石。本文基於 Hibernate 參考文檔核心章節,系統梳理關鍵概念與最佳實踐。
| 層級 | 特性 | 生命週期 | 線程安全 |
|---|---|---|---|
| Configuration | 配置元數據、建立 SessionFactory | 應用啟動時一次性 | 是 |
| SessionFactory | 重量級、不可變、緩存映射元數據 | 應用存活期內單例 | 是(所有線程共享) |
| Session | 輕量級、非線程安全、代表單個工作單元 | 請求/事務範圍內短暫存在 | 否(嚴禁多線程共享) |
最常用、最推薦的模式:一個 HTTP 請求對應一個 Session 與一個資料庫事務。
// 攔截器/Filter 中實現
public void doFilter(request, response, chain) {
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
// 綁定 Session 到當前線程(ThreadLocal)
CurrentSessionContext.bind(session);
chain.doFilter(request, response); // 執行業務邏輯
tx.commit(); // 提交事務、flush 變更
} catch (Exception e) {
if (tx != null) tx.rollback();
throw e;
} finally {
CurrentSessionContext.unbind();
session.close(); // 釋放 JDBC 連線
}
}
sessionFactory.getCurrentSession() 取得當前 Session// 錯誤:每次 DB 操作開關 Session
public User getUser(Long id) {
Session s = factory.openSession();
User u = s.get(User.class, id);
s.close();
return u;
}
public void updateUser(User u) {
Session s = factory.openSession();
s.update(u);
s.close();
}
後果:無法保證原子性、大量開關 Session 開銷大、無法利用一級快取、延遲載入失效。
業務邏輯應將多個資料庫調用組合為單一原子工作單元,對應一個 Session 與一個事務。
Web 應用常見場景:用戶編輯表單 → 多次請求修改草稿 → 最後提交保存。跨請求的資料庫事務不可接受(鎖太久、併發差)。
解決方案:樂觀鎖 + 脫管物件
@Version 欄位(整數或時間戳)session.merge(detachedObj)),Hibernate 自動檢查版本號,衝突拋 StaleObjectStateException瞬時態 ----save()/persist()----> 持久態 ----close()/evict()----> 脫管態
^ | |
| v |
| 修改屬性 (自動 dirty checking) |
| | |
+----delete() <----------------------+ |
| | |
+----load()/get() <------------------+ |
| | |
+----merge() <-----------------------+ |
| | |
+----update()/saveOrUpdate() <-------+ |
將 Session 範圍延伸至 View 渲染階段,解決延遲載入異常。優點:View 可自由導航關聯。缺點:事務過長、資料庫連線佔用久、業務邏輯與視圖耦合。建議:Service 層預先初始化所需關聯(fetch join、@EntityGraph、DTO 組裝),避免在 View 觸發資料庫查詢。
@Version + 脫管物件合併下篇將深入探討非托管環境、JTA 整合(BMT/CMT)、異常處理與連線釋放策略。