概述#

Persistence Context 作為 transactional write-behind cache,Hibernate Session 同時扮演 first-level cache 和 entity state transition buffer 的角色。EntityManagerSession 提供多種 entity state management 方法:

  • persist:將 transient entity 轉為 managed 狀態
  • merge:將 detached entity 的內部狀態複製到新載入的 entity instance
  • update / saveOrUpdate / lock:Hibernate 特有的 reattaching 方法,不需要重新載入 entity
  • remove / delete:將 entity 標記為移除

Persistence Context 透過 flushing 將記憶體中的 persistent state 同步到底層資料庫。在 flush 時,Hibernate 可偵測 managed entity 是否已被修改,並觸發對應的 table row update,這個過程稱為 dirty checking

使用 write-behind cache 的優勢:

  • Entity state changes 被 buffer 後可延遲執行,最小化 row-level lock 的持有時間
  • 一次性執行所有 statement,可利用 JDBC batch updates 避免逐條 roundtrip

中間層的 write-behind cache 也帶來挑戰:Persistence Context 可能產生 data inconsistency。理解 flushing 的內部運作機制,才能在不影響 data consistency 的前提下進行最佳化。

Flush Modes#

Persistence Context 可透過手動或自動方式 flush。EntityManagerSession 都提供 flush() 方法,用於手動觸發同步。

手動 flush 的時機:

  • Query 執行前:確保 in-memory 的變更對當前 query 可見,防止 read-your-own-writes 一致性問題
  • Transaction commit 前:確保 in-memory 的變更被持久化,避免 Persistence Context 關閉後遺失

JPA Flush Mode Types#

JPA 定義兩種自動 flush mode:

  • FlushModeType.AUTO(預設):在每個 query(JPQL 或 native SQL)執行前及 transaction commit 前觸發 flush
  • FlushModeType.COMMIT:僅在 transaction commit 前觸發 flush

Hibernate Flush Modes#

Hibernate 定義四種 flush mode:

  • FlushMode.AUTO(預設):在 transaction commit 時 flush;但不一定在每個 query 前 flush,採用 smart flushing 機制
  • FlushMode.ALWAYS:在每個 query(HQL 或 native SQL)和 transaction commit 前都 flush
  • FlushMode.COMMIT:僅在 transaction commit 時 flush
  • FlushMode.MANUAL:停用自動 flush,只能手動呼叫 flush()

JPA 的 FlushModeType.AUTO 較接近 Hibernate 的 FlushMode.ALWAYS,而非 FlushMode.AUTO。Hibernate 的 FlushMode.AUTO 不會在每個 query 前觸發 flush。

FlushMode.AUTO 的 SQL Query 一致性#

Hibernate 的 FlushMode.AUTO 採用 smart flushing 機制:執行 HQL query 時,Hibernate 會檢查當前 query 涉及的 table space,僅在有 pending entity state transition 匹配該 table space 時才觸發 flush。此最佳化可減少不必要的 flush 呼叫。

但對於 native SQL query,Hibernate 無法解析其涉及的資料庫 table,因此會在每個 native SQL query 前一律 flush。若要避免此行為,開發者可以:

  • 將 query 的 flush mode 設為 FlushMode.ALWAYS 以確保一致性
  • 使用 addSynchronizedEntityClass() 明確告知 Hibernate 該 native query 涉及哪些 table space

僅 Hibernate native API(Session)使用 smart flushing。使用 JPA EntityManager 時,Hibernate 會在每個 JPQL 或 native SQL query 前 flush。

Events and the Action Queue#

每個 entity state change 在 Hibernate 內部會產生對應的 event(如 PersistEventMergeEventDeleteEvent),並由對應的 event listener 處理(如 DefaultPersistEventListenerDefaultMergeEventListenerDefaultDeleteEventListener)。

Event listener 將 entity state transition 轉換為內部的 EntityAction,在 flush time 才實際執行:

  • EntityInsertAction / EntityIdentityInsertAction:transient entity 變為 persistent 時產生,觸發 SQL INSERT。使用 identity generator 時,INSERT 必須立即執行以取得 identifier
  • EntityUpdateAction:managed entity 被修改時產生,觸發 SQL UPDATE
  • EntityDeleteAction:entity 被標記為 removed 時產生,觸發 SQL DELETE
  • OrphanRemovalAction:帶有 orphan removal 的 association 在 dereferencing child entity 時產生

所有 pending entity actions 儲存在 ActionQueue 中,在 flush time 統一執行。

Entity state transitions 可透過 cascade 從 parent entity 傳播到 child entity。例如 cascading persist 時,Hibernate 的行為等同於對每個 child entity 手動呼叫 persist

Flush Operation Order#

Flush 時,Hibernate 以嚴格的固定順序執行所有 EntityAction

  1. OrphanRemovalAction
  2. EntityInsertActionEntityIdentityInsertAction
  3. EntityUpdateAction
  4. CollectionRemoveAction
  5. CollectionUpdateAction
  6. CollectionRecreateAction
  7. EntityDeleteAction

這個固定的執行順序可能導致非預期的 constraint violation。例如先 remove 舊 entity 再 persist 同 unique key 的新 entity 時,由於 INSERT 排在 DELETE 之前執行,會觸發 unique constraint violation。解決方式是在 remove 之後手動呼叫 flush(),確保 DELETE 先被執行。

實務上更好的做法是直接 UPDATE 既有 entity 而非先刪除再新增,因為 UPDATE 比 INSERT + DELETE 組合更有效率。

Dirty Checking#

Entity 從 transient 變為 managed 時,Hibernate 發出 SQL INSERT;entity 被標記為 removed 時,發出 SQL DELETE。UPDATE 則不同——它沒有對應的顯式 entity state transition,而是由 Persistence Context 追蹤 entity 的內部狀態,在 flush time 將修改過的 entity 轉換為 UPDATE statement。

The Default Dirty Checking Mechanism#

預設情況下,Hibernate 在 entity 載入時會在 Persistence Context 中保存一份 loading-time snapshot(以 Object[] 陣列表示,接近底層 table row 的值)。在 flush time,每個 entity attribute 都會與其 loading-time value 比較。

Dirty check 的次數由以下公式決定:

N = n * p(n = managed entities 數量,p = entity attributes 數量)

即使只有一個 attribute 被修改,Hibernate 仍需遍歷所有 managed entities。當 managed entities 數量龐大時,default dirty checking 會對 CPU 資源造成顯著影響。

Figure 12.1: Default dirty checking mechanism

Controlling the Persistence Context Size#

由於 loading-time snapshot 獨立保存,Persistence Context 儲存一個 managed entity 需要 兩倍記憶體。控制 Persistence Context 大小的策略:

  • Read-only mode:不需修改的 entity 應以 read-only 方式載入,可在 Session 層級或 query 層級設定:

    • Session 層級:session.setDefaultReadOnly(true)
    • Query 層級:.setHint(QueryHints.HINT_READONLY, true)
  • Read-only entity 不保存 loading-time snapshot,也不參與 dirty checking,同時節省 記憶體和 CPU

  • Batch processing:大量 entity 操作時,應定期 flush()clear() Persistence Context,並將 Persistence Context 跨多個 database transaction 分割,每個 batch iteration 清空後開始新的 transaction

Persistence Context 應盡量保持小規模。只有需要修改的 entity 才應成為 managed 狀態。Read-only transaction 應使用 DTO projections 或以 read-only mode 載入 entity。

Bytecode Enhancement#

Hibernate 長期支援 bytecode enhancement,但在 Hibernate 5 之前 dirty checking 並未使用此功能。Hibernate 5 重新實作了 bytecode instrumentation 機制,可避免基於 reflection 的 dirty checking。

Bytecode enhancement 可在 compile-time、runtime 或 deployment 時進行,建議使用 compile-time 方式:

  • Enhanced classes 可被 unit test 涵蓋
  • 應用程式啟動更快(不需 runtime instrumentation)
  • 避免 class loading 問題

Bytecode enhancement plugin 支援三個 instrumentation 選項:

  • Lazy initialization:允許 entity attributes 延遲載入
  • Dirty tracking:entity 自行追蹤 attribute 變更
  • Association management:自動同步 bidirectional association 的兩端

啟用 dirty tracking 後,Hibernate 會在 entity class 中注入 $$_hibernate_tracker 屬性。每個 setter method 被修改為同時呼叫 $$_hibernate_trackChange 方法記錄變更。在 flush time,Hibernate 透過 $$_hibernate_hasDirtyAttributes$$_hibernate_getDirtyAttributes 方法快速判斷 entity 是否被修改,無需逐一比較所有屬性。

效能測試顯示,在 50 個以下的 entity 數量時,reflection-based 和 bytecode enhancement 的 dirty checking 效能相當。當 entity 數量達到 100 個以上時,bytecode enhancement 才開始展現優勢。由於單次 dirty checking 的時間相較於 database query 仍然極短,in-memory attribute tracking 的實際效能提升在多數場景中並不顯著。

Figure 12.2: Bytecode enhancement performance gain