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

NHibernate 2.1.2 延遲載入機制深度解析:Proxy 攔截器與 LazyInitializer 全流程

     編輯:關於.NET

NHibernate 延遲載入核心在於 **動態 Proxy 攔截器** 攔截屬性/方法存取,觸發 **LazyInitializer** 載入實體。本文基於 NHibernate 2.1.2 原始碼架構,完整剖析從 SessionFactory 建置、Proxy 建立、攔截處理到實體載入的全流程。

整體架構圖

NHibernate 延遲載入架構圖

SessionFactory 建置階段

  1. 依 proxyfactory.factory_class 設定建立 IProxyFactoryFactory 實例 (預設 Castle / LinFu / Spring)
  2. use_proxy_validator=true (預設):載入實體中繼資料後,呼叫 ProxyValidator 驗證實體符合 Proxy 條件:
    • 非密封類別
    • 預設建構子 (public/protected)
    • public/protected virtual 方法/屬性 (供攔截)
  3. 為每個需 Proxy 的實體型別建立 IProxyFactory,呼叫 PostInstantiate 記錄元資料:
    • EntityName、PersistentClass、Interfaces (含 INHibernateProxy)
    • 單一/複合主鍵 Get/Set 方法
  4. ProxyFactory 實例快取,避免重複建立

Proxy 建立流程 (IProxyFactory.GetProxy)

  1. 建立 LazyInitializer 實例 (實作動態 Proxy 庫的攔截器介面),注入:
    • Session、Entity Key (ID)、實體型別、EntityMode、Factory 參考
  2. 呼叫動態 Proxy 庫 (Castle/LinFu/Spring) 建立 **Class Proxy** 或 **Interface Proxy**
  3. 將 INHibernateProxy 作為 **Mixin 介面** 混入 Proxy,內部用於識別 Proxy 與取得 LazyInitializer

延遲載入觸發與處理 (LazyInitializer)

不觸發載入的情況 (BasicLazyInitializer.Invoke)

  • 讀取 **主鍵值** (ID 已知,無需載入)
  • 讀取 **複合主鍵元件** 的 ID 值
  • 呼叫 Object 基類未覆寫方法 (GetType(), GetHashCode(), Equals(), ToString() — 若未 override)
  • 呼叫 Dispose()

觸發載入:AbstractLazyInitializer.Initialize()

  1. 檢查是否已載入 (_target != null) → 已載入直接返回
  2. 依序嘗試載入來源:
    1. 一級快取 (Session):session.Get(entityType, id)
    2. 二級快取 (Cache Provider):cache.Get(id)
    3. 資料庫:執行 SQL SELECT,經 EntityPersister 具現化實體
  3. 載入成功:實體放入一級/二級快取,設定 _target = loadedEntity
  4. 後續攔截呼叫:**透過反射轉發至 _target 實體對應方法/屬性**

關鍵點:Proxy 與實體為獨立物件

  • NHibernate **不將載入的屬性值複製回 Proxy**,而是保持 Proxy 為殼,**真實實體獨立存在**
  • 原因:支援 Interface Proxy (無實體基類)、載入邏輯獨立、Client 可透過 INHibernateProxy 同時取得 Proxy 與實體

從 Proxy 取得真實實體 (非官方 API,慎用)


ISessionFactory sf = new Configuration().Configure().BuildSessionFactory();
using (ISession session = sf.OpenSession())
{
    // 僅建立 Proxy,未觸發載入
    MyUser proxy = session.Load(11);
    Console.WriteLine(proxy.GetType().FullName); // NHibernate 產生的 Proxy 類別名

    // 呼叫 override ToString() → 觸發載入
    Console.WriteLine(proxy.ToString());

    // 取得真實實體
    NHibernate.Proxy.INHibernateProxy nhProxy = proxy as INHibernateProxy;
    MyUser real = nhProxy.HibernateLazyInitializer.GetImplementation() as MyUser;
    Console.WriteLine(real.GetType().FullName); // 真實 MyUser
    Console.WriteLine(real.ToString());
}
sf.Close();

執行結果

Proxy 與實體型別輸出

Proxy 庫比較與選擇建議

Proxy 庫優點缺點建議場景
Castle DynamicProxy成熟穩定、效能佳、支援介面/類別 Proxy依賴較大預設首選
LinFu DynamicProxy輕量、無外部依賴維護較少嵌入式/輕量專案
Spring.NET AOP Proxy整合 Spring 容器需 Spring.NET 執行環境已使用 Spring.NET 專案

常見問題與避坑

  1. LazyInitializationException:Session 關閉後存取未載入關聯 → 確保 Session 存活至視圖渲染結束 (Open Session In View 模式)。
  2. Proxy 衝突:實體實作介面但未標記 virtual → 驗證失敗,啟動拋異常。
  3. 效能陷阱:N+1 查詢 → 使用 FetchMode.Join、FetchMode.Subselect 或 HQL FETCH 預先載入。
  4. 序列化問題:Proxy 含非序列化欄位 → DTO 投影或自訂序列化代理。

總結

NHibernate 延遲載入本質:**動態 Proxy 攔截 → LazyInitializer 雙重檢查鎖載入 → 真實實體獨立存放 → 反射轉發**。理解 IProxyFactoryFactory、LazyInitializer、INHibernateProxy 三大核心介面職責,能精準診斷 LazyInitializationException、Proxy 驗證失敗、序列化失效等疑難雜症,並在效能調優時做出正確的 Fetch Strategy 決策。

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