Why JPA and Hibernate Matter#
雖然 JDBC 提供了良好的通用 API 來隱藏資料庫廠商的通訊協定差異,但它仍有以下不足:
- API 過於冗長,即使是簡單任務也需要大量程式碼
- Batching 對資料存取層不透明,需要使用與非批次完全不同的 API
- 缺乏對 explicit locking 和 optimistic concurrency control 的內建支援
- 對於 local transactions,資料存取與交易管理語意糾纏在一起
- 擷取 joined relations 時需要額外處理,將
ResultSet轉換為 Domain Model 或 DTO 物件圖
ORM (Object-Relational Mapping) 工具的首要目標是自動將物件狀態轉換翻譯為 SQL 語句,而本章旨在展示 Hibernate 如何解決上述 JDBC 的缺點。
Java Persistence 歷史:EJB 1.1 的 Entity Beans 因效能問題失敗;Hibernate 作為開源專案崛起,成為事實上的 Java 持久化框架。最終 Java Community Process 以 Hibernate 和 TopLink 為基礎制定了 JPA (Java Persistence API) 規範,成為標準的 Java 持久化技術。
The Impedance Mismatch#
當透過物件導向程式操作關聯式資料庫時,兩種不同的資料表示方式會開始衝突:
- 關聯式資料庫:資料儲存在表格中,透過關聯代數定義資料關聯
- 物件導向程式語言:物件同時擁有狀態和行為,允許雙向關聯
這兩種截然不同的方法之間的融合負擔,長久以來一直困擾著企業系統。ORM 設計模式正是用來橋接這兩種資料表示,縮小技術鴻溝:
- 每個資料庫列對應一個 Domain Model 物件(JPA 術語中稱為 Entity)
- ORM 工具負責將 entity 狀態轉換翻譯為 DML 語句
- 從應用開發角度看,操作 Domain Model 的關聯比直接撰寫底層 SQL 語句更為直覺

Figure 7.1: Object/Relational Mapping
JPA vs. Hibernate#
- JPA 僅是一個規範,定義了客戶端操作的介面以及標準的 object-relational mapping metadata(Java annotations 或 XML descriptors)
- Hibernate 在 JPA 規範發佈前就已經是完整的 Java ORM 實作,它實作了 JPA 規範,同時保留原生 API 以支援向後相容及非標準功能
如果 JPA 是介面,Hibernate 就是實作,而從效能角度來看,實作細節至關重要。
Hibernate 非標準功能#
Hibernate 提供了許多超越 JPA 規範的功能,對高效能資料存取尤為重要:
- 擴展的 identifier generators(hi/lo, pooled, pooled-lo)
- 透明的 prepared statement batching
- 可自訂的 CRUD 語句(
@SQLInsert,@SQLUpdate,@SQLDelete) - 靜態/動態 entity/collection filters(
@FilterDef,@Filter,@Where) - 將屬性映射到 SQL 片段(
@Formula) - Immutable entities(
@Immutable) - 更多 flush modes(
FlushMode.MANUAL,FlushMode.ALWAYS) - 透過 natural key 查詢 second-level cache
- Entity-level cache concurrency strategies
- 透過 HQL 進行 versioned bulk updates
- 排除特定欄位的 optimistic locking check
- Versionless optimistic locking(
OptimisticLockType.ALL,OptimisticLockType.DIRTY) - 支援 skip(無等待)pessimistic lock requests
- 支援 multitenancy
JPA 的實作細節可能洩漏且影響效能。例如
FlushModeType.AUTO對 native SQL 查詢不會觸發 flush;FetchType.EAGER的行為取決於是透過EntityManager還是 JPQL 查詢載入 entity。因此理解 Hibernate 的實作行為非常重要。
Portability 的考量#
- 追求可移植性而放棄資料庫特有功能可能影響效能
- 實務上,遇到資料存取效能問題比需要遷移資料庫或 JPA provider 更為常見
- 透過分層架構,切換 JPA 實作其實已經相當容易
Schema Ownership#
由於資料表示的二元性,schema 的所有權一直存在爭議。雖然理論上 Domain Model 和資料庫都能驅動 schema 演進,但實務上 schema 歸屬於資料庫。
原因在於:
- 一個企業系統可能被拆分為多個子系統(Web 應用、整合服務、搜尋引擎、批次處理器等),它們都需要使用底層資料庫
- 關聯式資料庫的 concurrency model 提供強一致性保證,使其適合作為中央整合點
- 資料庫系統支援多種 replication topologies,可擴展容量

Figure 7.2: Database-centric integration
對於超大型企業系統,資料分散在不同提供者(關聯式資料庫、快取、Hadoop、Spark)時,可使用 Apache Kafka 等 distributed commit log 作為整合點。

Figure 7.3: Distributed commit log integration
Schema 遷移#
- 無論選擇哪種架構風格,都需要將瞬態的 Domain Model 與底層持久化資料對應
- 資料 schema 隨企業系統演進,兩種 schema 表示必須保持一致
- 即使資料存取框架可以自動生成 schema,資料庫 schema 仍必須以增量方式遷移,且所有變更需在 VCS 中可追蹤
- 工具如 Flyway 可以自動化資料庫 schema 遷移
Entity State Transitions#
JPA 將開發者的思維從 SQL 語句轉移到 entity 狀態轉換。Entity 可處於以下四種狀態:
| 狀態 | 說明 |
|---|---|
| New (Transient) | 新建的 entity,尚未映射到任何資料庫列。一旦變為 managed,Persistence Context 會在 flush 時發出 INSERT |
| Managed (Persistent) | 與資料庫列關聯,由當前 Persistence Context 管理。狀態變更由 dirty checking 機制偵測,在 flush 時傳播為 UPDATE |
| Detached | 當 Persistence Context 關閉後,先前的 managed entities 變為 detached。後續變更不再被追蹤,不會自動同步到資料庫 |
| Removed | Entity 被排程刪除,實際的 DELETE 語句在 Persistence Context flush 時執行 |
狀態轉換操作#
- JPA:
persist(entity)/merge(entity)/remove(entity)/detach(entity)/clear()/close()

Figure 7.4: JPA entity state transitions
- Hibernate 額外提供:
save(entity)/update(entity)/delete(entity)

Figure 7.5: Hibernate entity state transitions
- 從資料庫載入 entity 到 Managed 狀態:
find()/getReference()/createQuery().getResultList()等
Persistence Context 捕獲 entity 狀態變更,並在 flushing 時將其翻譯為 SQL 語句。JPA 的
EntityManager和 Hibernate 的Session是操作 Persistence Context 的閘道介面。

Figure 7.6: Persistence Context
Write-based Optimizations#
SQL Injection 防護#
- JPA 工具管理 SQL 語句生成,降低 SQL injection 風險
- Hibernate 專門使用
PreparedStatement(s),不僅防範 SQL injection,還能利用 server-side 和 client-side statement caching
自動生成 DML 語句#
- JPA provider 自動生成 INSERT 和 UPDATE 語句,能輕鬆適應資料庫表結構變更
- 更新 entity model schema 後,Hibernate 會自動調整對應的修改語句
- Hibernate 也允許自訂所有 CRUD 語句,由開發者自行維護
Write-behind Cache#
Persistence Context 作為交易性的 write-behind cache 運作,將 entity 狀態 flushing 延遲到最後一刻:
- 所有修改都在記憶體中累積,在 flush 時一次性寫入資料庫
- 這種機制可以縮短資料庫鎖的持有時間,從而提升並發效能
快取會帶來一致性挑戰。Persistence Context 在執行任何 JPQL 或 native SQL 查詢前需要進行 flush,否則可能破壞 read-your-own-write 一致性保證。值得注意的是,Hibernate 在執行 native query 時不會自動 flush pending changes,開發者必須明確指示需要同步的資料庫表。
Transparent Statement Batching#
- 由於所有變更在同一時間 flush,Hibernate 可以將 JDBC 語句進行 batching
- Batch updates 可以在資料存取邏輯實作完成後透明地啟用
- 只需一個配置,Hibernate 就能以批次方式執行所有 prepared statements
Application-level Concurrency Control#
- 在跨多個 web request 的 long conversation 中,資料庫 isolation level 無法防止 lost updates
- JPA 同時支援 optimistic locking 和 pessimistic locking
- Optimistic locking 透過 version 欄位實現 happens-before 事件排序,可透過 Extended Persistence Contexts 或 detached entities 在多個請求間維護舊 entity snapshots
- Pessimistic locking 在使用較低交易隔離等級時很實用;Hibernate 原生 API 還支援鎖定逾時和跳過已取得的鎖
Read-based Optimizations#
JDBC 的 ResultSet 是底層擷取資料的表格式表示,而 Domain Model 是 entity graph 結構,資料存取層必須將扁平的 ResultSet 轉換為階層式結構。
- 使用 JDBC 時,開發者必須手動解析關聯並建構階層式 entity 結構
- 使用 JPA 時,object-relation transformation 會根據 entity schema 自動完成
資料庫不能被抽象化,不能假裝 entities 像其他普通物件一樣操作。在讀取資料時,impedance mismatch 更加明顯,基於效能考量,必須時刻留意每次 fetching 操作背後的 SQL 語句。
Fetching 策略#
- Entity 關聯可以用物件關係表示,fetching 行為可以硬編碼在 entity schema 中,但這通常是很糟糕的做法
- 擷取多個 one-to-many 或 many-to-many 關聯可能產生 Cartesian Product,嚴重影響效能
- Fetching strategy 應該以每個查詢為基準來設定,而非固定在 entity schema 層級
使用 Projection 而非完整 Entity#
- 並非所有讀取操作都需要載入完整的 entity 及其所有關聯
- 對於唯讀視圖,使用 custom projection(僅選取需要的欄位)通常更合適
- 原則:當邏輯交易需要修改資料時才載入 entity;唯讀場景優先使用 projection
Second-level Cache#
- First-level cache(Persistence Context)的生命週期綁定在邏輯交易上,不能被多個並發交易共享
- Second-level cache 關聯到
EntityManagerFactory,所有 Persistence Contexts 都能存取,可儲存 entities、entity associations 和 query results - JPA 未強制要求 second-level cache,Hibernate 預設停用(相對於 EclipseLink 預設啟用)
Second-level cache 可以緩解 entity fetching 效能問題,但 caching 本質上是一致性和效能之間的取捨。在分散式環境中,需要 distributed caching 實作,而網路開銷可能仍然存在。
Wrap-up#
- 橋接物件導向和關聯式兩種高度特化的技術始終是困難的問題
- Object-relational impedance mismatch 不可避免,ORM 模式旨在縮小差距但無法完全消除
- 所有通訊最終都流經 JDBC,所有執行都發生在資料庫引擎中
- 高效能的企業應用必須與底層資料庫系統共鳴,ORM 工具不應破壞這層關係
- Hibernate 是一個非常複雜的框架,需要對資料庫系統、JDBC 和框架本身都有深入理解