前言:JPA 緩存體系的基石 — Persistence Context
JPA (Java Persistence API) 將「緩存」概念具體化為 Persistence Context (持久化上下文)。它不僅是實體緩存容器,更是實體狀態管理 (New/Managed/Detached/Removed) 的核心。JPA 2.0 (JSR 317) 在 1.0 基礎上完善了上下文傳播、擴展上下文、鎖模式等機制。本文聚焦一級緩存 (L1) 的核心原理、兩種上下文類型、實體生命週期管理、以及常見誤區與最佳實踐。
1. Persistence Context 本質
- 身份標識保證:同一上下文中,同一實體類 + 同一主鍵 → 唯一實例 (== 比較為 true)。
- 寫延遲:實體狀態變更 (setter、關聯操作) 累積在上下文,事務提交/flush 時批量生成 SQL。
- 脏檢查:flush 時比對實體當前狀態與載入時快照,僅更新變更列。
- 一級緩存:
find()、JPQL 查詢結果優先從上下文獲取,未命中再發 SQL。
2. 兩種 Persistence Context 類型
| 特性 | 事務型 | 擴展型 |
| 註解 | @PersistenceContext (預設) | @PersistenceContext(type=EXTENDED) |
| 生命週期綁定 | JTA 事務 / Resource Local 事務 | Stateful Session Bean 實例 |
| 跨事務保持 | 否 (事務結束 → 實體 detached) | 是 (跨請求/事務保持 managed) |
| 典型注入組件 | Stateless EJB、Spring @Transactional、Servlet | Stateful EJB、CDI @ConversationScoped |
| 適用場景 | 短請求、CRUD、高併發 | 長會話、向導表單、購物車、多步驟業務 |
3. 實體狀態流轉與上下文操作
persist() flush/commit
NEW ----------> MANAGED ----------> DATABASE
^ detach/close/clear ^
| |
| merge() |
+---------- DETACHED <--------------+
| ^
| | remove()
+---- REMOVED -+
關鍵操作語義
persist(entity):NEW → MANAGED,納入上下文管理,事務提交時 INSERT。
merge(detached):將 detached 狀態拷貝到 MANAGED 實例 (若上下文已有則更新,否則載入/新建),返回 MANAGED 實例。**原 detached 實例不變**。
remove(managed):MANAGED → REMOVED,事務提交時 DELETE。
refresh(managed):用 DB 當前數據覆蓋實體 (放棄未 flush 變更)。
detach(managed) (JPA 2.0+):MANAGED → DETACHED,從上下文移除但保留實體狀態。
clear():清空上下文所有實體 → 全部變 DETACHED。
4. 事務型上下文實戰模式
模式 A:請求範圍 (Spring @Transactional / Stateless EJB)
@Transactional
public UserDTO getUser(Long id) {
User user = em.find(User.class, id); // L1 緩存命中或查 DB
return UserDTO.from(user); // 事務結束,user 變 detached
}
- 同一事務內多次
find(id) 返回同一實例,避免重複查詢。
- 返回 DTO 而非實體,防止視圖層觸發懶加載 (LazyInitializationException)。
模式 B:手動 Flush 控制批次寫入
@Transactional
public void batchImport(List dtos) {
int i = 0;
for (UserDTO dto : dtos) {
em.persist(User.from(dto));
if (++i % 50 == 0) {
em.flush(); // 批量發送 INSERT
em.clear(); // 清空 L1,釋放內存,防止 OOM
}
}
}
5. 擴展上下文實戰模式與風險控制
長會話模式 (向導式表單)
@Stateful
@ConversationScoped // CDI 對話範圍
public class OrderWizard {
@PersistenceContext(type = EXTENDED)
EntityManager em;
private Order order = new Order(); // 全程 MANAGED
public void addLine(LineDTO dto) {
order.addLine(Line.from(dto)); // 直接操作 managed 集合
}
@Remove
@Transactional
public void submit() {
em.persist(order); // 最後統一持久化
}
}
風險與對策
| 風險 | 對策 |
| 內存洩漏 (實體累積) | 定期 em.clear()、限制會話時長、對話超時銷毀 |
| 樂觀鎖衝突 (版本號過期) | 提交前 em.lock(entity, LockModeType.OPTIMISTIC_FORCE_INCREMENT)、或重新載入合併 |
| 跨事務一致性 (中間狀態可見) | 僅在最後提交事務,中間步驟不開啟事務 (REQUIRES_NEW 除外) |
| 序列化問題 (Stateful Bean 釋活/鈍化) | 實體實現 Serializable、避免攜帶非序列化引用 (如 EM) |
6. 常見誤區與避坑指南
- 誤區:
merge() 會更新傳入對象。
真相:返回 新的 managed 實例,原對象保持 detached。後續操作必須用返回值。
- 誤區:L1 緩存可跨請求共享。
真相:事務型上下文隨事務結束銷毀,擴展型綁定 Stateful Bean/對話範圍,非全局共享。
- 誤區:
find() 不會觸發懶加載。
真相:find() 按實體定義的 fetch 預策略載入 (EAGER/LAZY),僅 JPQL/EntityGraph 可覆蓋。
- 誤區:大量數據導入用
persist() 循環。
真相:需批次 flush() + clear(),或改用 Stateless Session / JDBC 批次 / Spring Data JPA saveAll (底層仍需手動優化)。
7. 監控與診斷
hibernate.generate_statistics=true → JMX Statistics MBean:查看 entityLoadCount、entityFetchCount、secondLevelCacheHitCount (L2)。
- Spring Boot Actuator:
/actuator/metrics/hibernate.cache.*。
- 慢查詢日誌 +
hibernate.show_sql=true + hibernate.format_sql=true 關聯分析。
結語
Persistence Context 是 JPA 實現「透明持久化」的核心引擎。深入理解其兩種類型、實體狀態機、寫延遲/脏檢查機制、上下文邊界,是避免 LazyInitializationException、失控的 SQL 數量、內存洩漏、樂觀鎖衝突等經典坑的前提。下篇將進入 JPA 2.0 標準化的二級緩存 (L2) 領域:@Cacheable、shared-cache-mode、CacheRetrieveMode/StoreMode、標準 Cache 接口,以及與 Hibernate 豐富擴展的映射關係。
- 上一篇文章: JPA 2.0 vs Hibernate 緩存機制深度對比(一):JPA 2.0 標準緩存 API 與二級緩存規範
- 下一篇文章: Java EE 6 Web 開發新範式:EJB 3.1 內嵌於 WAR 實戰 (上)
Copyright ©
程式碼之家 All Rights Reserved