| 特性 | get() | load() |
|---|---|---|
| 立即查詢數據庫 | 是(緩存未命中時) | 否(支持延遲加載) |
| 支持 Lazy Loading | 否 | 是 |
| 數據不存在時 | 返回 null | 拋出 ObjectNotFoundException |
| 返回對象類型 | 實體或代理 | 實體或代理 |
| 緩存檢查順序 | 一級→二級→數據庫 | 一級→二級→數據庫 |
記錄當前 Session 中查詢無結果的條件,避免重複無效查詢。
public Object get(Class clazz, Serializable id) {
// 1. 檢查一級緩存
Object obj = session.get(id);
if (obj != null && isValid(obj)) return obj;
// 2. 檢查 NonExists
if (nonExists.contains(id)) return null;
// 3. 檢查二級緩存
obj = secondLevelCache.get(id);
if (obj != null) return obj;
// 4. 查詢數據庫
obj = loadFromDatabase(id);
if (obj != null) {
session.put(obj); // 放入一級緩存
return obj;
}
// 5. 記錄 NonExists,返回 null
nonExists.add(id);
return null;
}
public Object load(Class clazz, Serializable id) {
// 1. 檢查一級緩存
Object obj = session.get(id);
if (obj != null && isValid(obj)) return obj;
// 2. 檢查二級緩存
obj = secondLevelCache.get(id);
if (obj != null) return obj;
// 3. 創建代理對象(不立即查詢數據庫)
if (isLazyLoadingEnabled(clazz)) {
obj = createProxy(id); // 只有調用方法時才查詢
session.put(obj);
return obj;
}
// 4. 非延遲模式或無代理情況,查詢數據庫
obj = loadFromDatabase(id);
if (obj != null) {
session.put(obj);
return obj;
}
// 5. 記錄 NonExists,拋出異常
nonExists.add(id);
throw new ObjectNotFoundException(id, clazz.getName());
}
錯誤。兩者都可能返回代理對象,取決於:
@Proxy(lazy=true) 或配置 lazy="true"錯誤。get() 同樣優先讀緩存,只有緩存未命中才查數據庫。若其他事務已修改並提交,但緩存未失效,get() 讀到的仍是舊數據。
部分正確。load() 創建代理時不查數據庫,但首次調用代理方法時會觸發查詢。若數據不存在,此時拋出異常。
public void testGetVsLoad() {
Session session = HibernateUtils.getSession();
session.beginTransaction();
// 1. 數據不存在情況
System.out.println("get 不存在: " + session.get(User.class, "123")); // null
try {
session.load(User.class, "123");
} catch (ObjectNotFoundException e) {
System.out.println("load 不存在: 拋出異常");
}
// 2. 同一 Session 內 get/load 返回同一對象
User u1 = (User) session.get(User.class, "valid_id");
User u2 = (User) session.load(User.class, "valid_id");
System.out.println("同對象: " + (u1 == u2)); // true
System.out.println("u1 類: " + u1.getClass().getName());
System.out.println("u2 類: " + u2.getClass().getName());
// 3. 清空緩存後
session.clear();
User u3 = (User) session.load(User.class, "valid_id"); // 代理
User u4 = (User) session.get(User.class, "valid_id"); // 可能也是代理!
System.out.println("u3 類: " + u3.getClass().getName());
System.out.println("u4 類: " + u4.getClass().getName());
System.out.println("同對象: " + (u3 == u4)); // true
session.getTransaction().commit();
}
User u = (User) session.load(User.class, "123");
System.out.println(u.getId()); // 輸出 "123",不查數據庫!
原因:代理對象內部持有 ID,getId() 直接返回 ID 字段,不觸發延遲加載。只有訪問非 ID 字段才會查詢數據庫。
| 場景 | 推薦方法 | 理由 |
|---|---|---|
| 確定數據存在,需立即使用 | get() | 語義清晰,無異常處理 |
| 不確定數據存在,需判斷 | get() | 返回 null 便於業務判斷 |
| 確定存在,僅需 ID 關聯其他對象 | load() | 避免不必要的數據庫查詢 |
| 大量關聯對象批量處理 | load() | 延遲加載減少查詢 |
| 需要拋出異常中斷流程 | load() | 異常語義更明確 |
get() 和 load() 的核心差異在於異常處理語義和延遲加載支持,而非返回對象類型。兩者共享相同的緩存檢查邏輯,緩存命中時返回同一對象。理解 Session 緩存機制、代理創建時機、NonExists 黑名單,是正確使用 Hibernate、避免性能問題與邏輯錯誤的關鍵。