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

Hibernate Session.get vs load 深度解析:緩存機制、代理對象與異常行為

     編輯:Java 相關知識

核心結論先行

特性get()load()
立即查詢數據庫是(緩存未命中時)否(支持延遲加載)
支持 Lazy Loading否是
數據不存在時返回 null拋出 ObjectNotFoundException
返回對象類型實體或代理實體或代理
緩存檢查順序一級→二級→數據庫一級→二級→數據庫

Session 兩級緩存機制

一級緩存(內部緩存)

  • Session 級別,事務範圍
  • 存儲當前 Session 所有關聯實體
  • Key:實體類型 + ID

二級緩存

  • SessionFactory 級別,進程/集群範圍
  • 所有 Session 共享
  • 需配置緩存提供者

NonExists 記錄(查詢黑名單)

記錄當前 Session 中查詢無結果的條件,避免重複無效查詢。

get() 執行流程詳解


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;
}

load() 執行流程詳解


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());
}

關鍵誤區澄清

誤區 1:get 返回實體,load 返回代理

錯誤。兩者都可能返回代理對象,取決於:

  • 實體類是否標註 @Proxy(lazy=true) 或配置 lazy="true"
  • 緩存中是否已有代理對象
  • get() 在緩存命中時直接返回緩存對象(可能是代理)

誤區 2:get 總能讀到最新數據

錯誤。get() 同樣優先讀緩存,只有緩存未命中才查數據庫。若其他事務已修改並提交,但緩存未失效,get() 讀到的仍是舊數據。

誤區 3:load() 不查數據庫

部分正確。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();
}

特殊現象:load 後直接 getId()


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、避免性能問題與邏輯錯誤的關鍵。

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