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

Hibernate 3 學習筆記(二十二):Query 快取機制深度解析

     編輯:Java 相關知識

前言

在前一篇文章中,我們探討了 Hibernate 的基本查詢操作。本篇將深入剖析 Query Cache(查詢快取)的運作原理、啟用方式,以及 list() 與 iterate() 兩種查詢方法在快取行為上的關鍵差異,幫助你在實際專案中做出最佳效能選擇。

一、預設行為:未啟用 Query Cache 時

我們先看一段基礎測試代碼,執行兩次相同的 HQL 查詢:

String hql = "from com.hb3.pack_22.model.User";
Query query = session.createQuery(hql);
List<?> users = query.list();
// ... 列印結果 ...
query = session.createQuery(hql);
users = query.list();
// ... 列印結果 ...

執行結果:

Hibernate: select user0_.id as id0_, user0_.name as name0_, user0_.room_id as room3_0_ from user user0_
chenyan
shenbin
Hibernate: select user0_.id as id0_, user0_.name as name0_, user0_.room_id as room3_0_ from user user0_
chenyan
shenbin

可以看到,每次呼叫 list() 都會發出實際的 SQL 查詢,資料庫被存取了兩次。這就是預設不開啟查詢快取的行為。

二、啟用 Query Cache 的兩個必要步驟

1. 修改 hibernate.cfg.xml

在 <session-factory> 區塊中加入:

<property name="hibernate.cache.use_query_cache">true</property>

2. 程式碼中明確設定 Query 為可快取

建立 Query 物件後,必須呼叫 setCacheable(true):

Query query = session.createQuery(hql);
query.setCacheable(true); // 關鍵行
List<?> users = query.list();

同樣執行兩次查詢,第二次將不再發出 SQL,直接從快取返回結果:

Hibernate: select user0_.id as id0_, user0_.name as name0_, user0_.room_id as room3_0_ from user user0_
chenyan
shenbin
chenyan
shenbin

三、Query Cache 運作核心邏輯

Hibernate 啟用查詢快取後,會以「查詢 SQL 字串 + 參數值」為鍵,儲存查詢結果的**識別值(通常是主鍵 ID)**。

  • 下次相同查詢時,先比對 SQL 是否相同。
  • 再檢查對應的資料庫表(User 表)自上次快取以來是否有 INSERT/UPDATE/DELETE 異動。
  • 若 SQL 相同且表無異動,直接拿快取中的 ID 去 Session/二級快取撈取實體物件,省去資料庫往返。
注意: Query Cache 快取的是「查詢結果的 ID 清單」,而非實體物件本身。實體物件仍依賴 Session 快取或二級快取。

四、list() vs iterate():快取行為大不同

這是許多開發者容易誤解的關鍵點。

1. list():不讀取 Query Cache,但會寫入

Query query = session.createQuery("from User");
List users = query.list(); // 第 1 次:查 DB,結果寫入 Query Cache
users = query.list();      // 第 2 次:**仍查 DB**,不讀 Query Cache!

SQL 輸出:

Hibernate: select ... from user
Hibernate: select ... from user  <-- 重複查詢!

list() 適合「一次性讀取所有資料」的場景,不依賴查詢快取讀取,但會更新快取內容。

2. iterate():讀取 Query Cache,逐筆載入實體

Query query = session.createQuery("from User");
Iterator users = query.iterate(); // 第 1 次:查 DB 取 ID,再逐筆載入實體
users = query.iterate();          // 第 2 次:讀 Query Cache 取 ID,再逐筆載入實體

SQL 輸出:

Hibernate: select user0_.id as col_0_0_ from user user0_  <-- 只撈 ID
Hibernate: select ... from user ... where user0_.id=?      <-- 逐筆撈實體
Hibernate: select ... from user ... where user0_.id=?
Hibernate: select user0_.id as col_0_0_ from user user0_  <-- 第 2 次只撈 ID
chenyan
shenbin  <-- 實體從 Session Cache 取得,無額外 SQL

iterate() 會將查詢到的 ID 存入 Query Cache,下次相同查詢直接讀取 ID 清單,再利用 Session 快取(一級快取)逐筆解析實體。

五、實戰效能對比:何時用 list?何時用 iterate?

我們設計一個場景:查詢 User 並存取其關聯的 Room(多對一),觀察 SQL 數量。

案例 A:純使用 iterate() 兩次

Iterator<?> it = query.iterate();
while(it.hasNext()) { User u = (User)it.next(); System.out.println(u.getName()); }
System.out.println("=== 分隔 ===");
it = query.iterate();
while(it.hasNext()) { User u = (User)it.next(); System.out.println(u.getName()); }

結果: 第一次查詢發出 1 條 ID 查詢 + N 條實體查詢(N=筆數);第二次只有 1 條 ID 查詢,實體從 Session Cache 取得。**但第一次的 N+1 查詢極其低效。**

案例 B:首次 list(),第二次 iterate()

List<?> list = query.list();
Iterator<?> it = list.iterator();
while(it.hasNext()) { User u = (User)it.next(); System.out.println(u.getRoom().getAddress()); }

System.out.println("=== 分隔 ===");
it = query.iterate();
while(it.hasNext()) { User u = (User)it.next(); System.out.println(u.getName()); }

結果: 第一次 list() 以 **單條 JOIN SQL** 取回所有 User 及 Room 資料;第二次 iterate() 讀 Query Cache 取 ID,實體從 Session Cache 直接拿,**零資料庫存取**。

結論: 首次查詢用 list()(配合 fetch join)效能最佳;後續相同查詢用 iterate() 利用快取極致提升效能。

六、記憶體管理提醒

iterate() 會將實體載入 Session 快取,大量資料時記憶體壓力大。必要時呼叫 session.evict(entity) 或 session.clear() 釋放。

七、總結

  1. Query Cache 需雙重開啟:設定檔 + setCacheable(true)。
  2. 快取內容是「ID 清單」,實體仍靠 Session/二級快取。
  3. list() 不讀 Query Cache,適合首次大量載入;iterate() 讀 Query Cache,適合重複查詢。
  4. 最佳組合:**首次 list()(含 fetch join)→ 後續 iterate()**。

善用這機制,能大幅減少資料庫負載,提升系統回應速度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved