首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> JSP 程式設計 >> 關於JSP

Hibernate 緩存機制與 CRUD 操作深度解析:一級/二級緩存、對象狀態、N+1 問題與優化策略

     編輯:關於JSP

前言

Hibernate 作為經典 ORM 框架,性能核心在於**緩存體系**。理解對象三種狀態、兩級緩存交互、CRUD 觸發的緩存行為,是避開性能陷阱、寫出高效持久層代碼的前提。

一、實體對象三種狀態

狀態別名定義關鍵特徵
Transient (瞬時)VO / DTOnew 出來,無 ID,未關聯 Session不在緩存,無數據庫記錄對應
Persistent (持久)PO關聯 Session,有 ID,處於 Session 生命週期內**一級緩存**、自動髒檢查、延遲加載、級聯操作
Detached (游離)VO / DTOSession 關閉/清理/evict 後的持久對象有 ID,無 Session,可序列化傳輸,需 reattach 才能持久化

狀態流轉:

new Entity() → Transient
save()/persist() → Persistent
Session.close()/evict() → Detached
update()/merge()/lock() → Persistent (再次關聯)
delete()/remove() → Removed (提交後 Transient)

二、緩存架構

1. 一級緩存 (Session 級,強制開啟)

  • **作用域**:單個 Session (通常 = 一個請求/事務)。
  • **結構**:`Map` (EntityKey = 類 + ID)。
  • **特性**:不可關閉、線程不安全、事務隔離由數據庫鎖保證。
  • **操作觸發**:
    • save()、get()、load()、update()、merge()、lock() 都會**放入/查找**一級緩存。
    • flush() 同步到數據庫,不清空緩存。
    • clear() / evict(entity) 手動清理。

2. 二級緩存 (SessionFactory 級,可選啟用)

  • **作用域**:全局,跨 Session/請求共享。
  • **啟用**:hibernate.cache.use_second_level_cache=true + 提供商 (Ehcache/Infinispan/Redis/Hazelcast)。
  • **緩存內容**:實體數據 (脫離 Session 的脫管狀態)、集合、查詢結果 (Query Cache)。
  • **併發策略**:
    • read-only:只讀參考數據,最高性能。
    • nonstrict-read-write:極少寫入,允許臨時不一致。
    • read-write:讀寫並發,使用軟鎖。
    • transactional:XA 事務緩存 (極少用)。

三、CRUD 與緩存交互細節

操作一級緩存二級緩存SQL 發送時機注意事項
save()放入**不放入** (默認)flush/commitID 生成策略影響 (identity 需立即 INSERT)
saveOrUpdate()查找→決策 save/update不直接操作flush/commit批量操作必須定期 flush()+clear() 防 OOM
delete()標記移除同步失效 (若啟用)flush/commit**批量刪除會先載入所有對象到內存** → OOM 風險,建議 HQL 批量刪除或分頁迭代
update()重新關聯 (游離→持久)同步失效flush (不立即)持久態對象調用 update 無效;髒檢查自動更新更優雅
merge()複製狀態到持久實例同步失效flush返回**新持久實例**,原游離對象不變
get()查找→無則查 DB→放入查找→無則查 DB→放入立即 (若無緩存)返回 null 表示不存在
load()查找→無則建代理→放入查找→無則建代理→放入**首次訪問屬性時** (延遲)不存在拋異常;適合「確信存在」場景
createQuery().list()**不讀緩存,只寫入** (實體放入一級)**不讀寫** (除非開 Query Cache)立即大結果集慎用,內存暴增
createQuery().iterate()查 ID 列表 (先查一級/二級) → 再按 ID 逐條 load查找 ID/實體N+1 查詢 (極慢)**僅當實體已在緩存** 才有優勢,否則慎用

四、N+1 問題與解決方案

現象

List orders = session.createQuery("FROM Order").list(); // 1 查詢
for (Order o : orders) {
    System.out.println(o.getCustomer().getName()); // N 查詢 (每個 Order 觸發 1 次)
}

解決

  1. 抓取策略:映射文件/註解 @ManyToOne(fetch = FetchType.EAGER) 或 HQL FROM Order o JOIN FETCH o.customer。
  2. 批量抓取:@BatchSize(size=20),Hibernate 會用 IN (id1, id2...) 批量載入關聯。
  3. 子查詢抓取:@Fetch(FetchMode.SUBSELECT),二次查詢載入所有關聯。

五、查詢緩存

  • 啟用:hibernate.cache.use_query_cache=true + 二級緩存提供商。
  • 使用:query.setCacheable(true)。
  • 緩存鍵:SQL + 參數 + 分頁信息。
  • 失效:任何相關表的增刪改提交時自動失效 (基於表空間時間戳)。
  • **適用場景**:頻繁讀、極少寫、參數固定的查詢 (如字典、配置、報表)。

六、批量操作最佳實踐

// 批量插入/更新
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for (int i = 0; i < 10000; i++) {
    Entity e = new Entity(...);
    session.save(e);
    if (i % 50 == 0) { // 與 JDBC 批處理 size 一致
        session.flush();
        session.clear(); // 關鍵:清一級緩存防 OOM
    }
}
tx.commit();
session.close();

// 批量刪除 (HQL 直通數據庫,不載入對象)
session.createQuery("DELETE FROM Entity WHERE status = :st")
       .setParameter("st", "OLD")
       .executeUpdate();

七、總結:緩存使用原則

  1. 默認信任一級緩存,同一 Session 內重複 get/load 極快。
  2. 二級緩存只給「讀多寫少、允許最終一致」的實體/集合 (字典、分類、配置)。
  3. 查詢緩存慎開,失效機制粗粒度 (表級),寫入頻繁表會導致緩存抖動。
  4. 批量寫必須 flush+clear,大結果集用流式/游標/分頁。
  5. 監控指標:緩存命中率、對象加載數、SQL 數、Session 內存佔用。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved