Session.load() 與 Session.get() 是 Hibernate 最易混淆的兩個查詢方法。本文從延遲加載機制、不存在記錄處理、緩存穿透邏輯、代理對象本質四個維度深度對比,並給出生產環境選型決策樹。
| 維度 | load() | get() |
|---|---|---|
| 延遲加載 | 是 (返回代理,首次訪問非 ID 屬性觸發 SQL) | 否 (立即查詢數據庫,返回實例或 null) |
| 記錄不存在 | 返回未初始化代理,訪問屬性拋 ObjectNotFoundException | 直接返回 null |
| 一級緩存 | 先查 Session 緩存,無則建代理 | 先查 Session 緩存 → 二級緩存 → 數據庫 |
| 二級緩存 | 訪問屬性時才查二級緩存/DB | 立即查二級緩存 → DB |
| 返回類型 | 代理對象 (繼承實體類或實現介面) | 實體實例 (或 null) |
| 適用場景 | 確信記錄存在、只需 ID、關聯導航 | 不確定存在、需立即用到屬性值 |
Interceptor.intercept() → 觸發 DefaultLoadEventListener → 查二級緩存 → 查 DB → 初始化代理實例SELECT ... WHERE id=?null// 假設 ID=999 不存在
User u1 = session.load(User.class, 999L); // 不查 DB,返回代理
System.out.println(u1.getId()); // 999 (只讀 ID,不觸發查詢)
System.out.println(u1.getName()); // 拋 ObjectNotFoundException
User u2 = session.get(User.class, 999L); // 立即查 DB
System.out.println(u2); // null,無異常
order.getUser()),外鍵約束保證 User 必存在,load 避免多餘查詢,極大提升關聯導航效能。@BatchSize(size=20) 可優化 load() 觸發的 N+1 問題。需要查詢實體
│
├─ 確定記錄必須存在 (外鍵約束、業務不變量)
│ └─ 僅需 ID 或稍後才用屬性 → load() ✅
│ └─ 立即需要屬性值 → get() 也可 (差異微小)
│
└─ 記錄可能不存在 (用戶輸入、外部參數)
├─ 需區分「不存在」與「錯誤」 → get() ✅ (返回 null)
└─ 可接受異常處理 → load() + try-catch (不推薦)
| 誤區 | 真相 |
|---|---|
| load() 永遠不查數據庫 | 首次訪問非 ID 屬性**必查** (除非一級/二級緩存命中) |
| get() 不支援延遲加載 | get() 返回已初始化實例,關聯屬性仍受 @OneToMany(fetch=LAZY) 控制 |
| load() 返回 null | **永不返回 null**,不存在拋異常 |
| 代理類就是實體類 | 代理繼承實體類 (CGLib) 或實現介面,instanceof 仍為 true,但 getClass() != Entity.class |
load() = 代理 + 延遲 + 異常 (信任外鍵/不變量)
get() = 實例 + 立即 + null (防禦性編程)
掌握二者在 Proxy 初始化時機、緩存查找順序、異常語義 的本質差異,是寫出健壯、高效 Hibernate 代碼的關鍵。