NHibernate之(13):初探立即載入機制
本節內容
- 引入
- 立即載入
- 例項分析
- 1.一對多關係例項
- 2.多對多關係例項
- 結語
引入
通過上一篇的介紹,我們知道了NHibernate中預設的載入機制——延遲載入。其本質就是使用GoF23中代理模式實現,這節我們簡單分析NHibernate另一種載入機制——立即載入。我用一張圖片形象的展現立即載入機制。
立即載入
顧名思義,就是立刻載入相關聯物件集合,與延遲載入相反。我們可以使用三種方法來立即載入,分別是:可選的lazy屬性、NHibernate提供的實用類、HQL抓取策略。下面依次用例項分析其中的機制。
例項分析
1.一對多關係例項
在一對多關係例項中,我們使用Customer物件與Order物件為例,在資料訪問層中依然使用上一篇的方法,這裡使用強制關閉Session的方法,為什麼使用Using強制釋放資源呢?我就是想利用這個來模擬Web應用程式中的Session機制。用這個分析比沒有Using釋放資源更有意義。
資料訪問層中方法:載入Customer物件並使用Using強制清理關閉Session
public Customer LazyLoadUsingSession(int customerId) { using (ISession _session = new SessionManager().GetSession()) { return_session.Get<Customer>(customerId); } }
1.使用lazy="false"屬性
在上一篇我們一直沒有修改對映檔案即一直預設是lazy="true",NHibernate就採用了預設的延遲載入。
這裡介紹第一種方法就是修改對映檔案來立即載入,開啟Customer.hbm.xml檔案,在Set元素中新增lazy="false"。
編寫一個測試驗證,呼叫資料訪問層中的使用Using強制資源清理Session載入Customer物件的方法載入一個Customer物件,NHibernate這時立即載入Customer相關聯的Order物件。利用NHibernate提供實用類(NHibernateUtil)測試被關聯的Customer物件集合是否已初始化(也就是已載入)。
[Test] public void EagerLoadUsingLazyFalseTest() { Customer customer = _relation.LazyLoadUsingSession(1); Assert.IsTrue(NHibernateUtil.IsInitialized(customer.Orders)); }
測試成功,證明NHibernate立即載入了Order物件,發現生成兩句SQL語句:第一條查詢Customer物件,第二條語句查詢其相關聯的Order物件集合。
SELECT customer0_.CustomerId as CustomerId9_0_, customer0_.Version as Version9_0_, customer0_.Firstname as Firstname9_0_, customer0_.Lastname as Lastname9_0_ FROM Customer customer0_ WHERE customer0_.CustomerId=@p0; @p0 = '1' SELECT orders0_.Customer as Customer1_, orders0_.OrderId as OrderId1_, orders0_.OrderId as OrderId6_0_, orders0_.Version as Version6_0_, orders0_.OrderDate as OrderDate6_0_, orders0_.Customer as Customer6_0_ FROM [Order] orders0_ WHERE orders0_.Customer=@p0; @p0 = '1'
不過,細心的朋友會發現,這時Orders物件集合的型別是Iesi.Collections.Generic.HashedSet`1[DomainModel.Entities.Order],上一節只有在沒有使用Using強制關閉資源下,Orders物件集合才是這個型別,在使用強制關閉資源的情況下,Orders物件集合的型別為:NHibernate.Collection.Generic.PersistentGenericSet<DomainModel.Entities.Order> ,進一步讀取Order項丟擲HibernateException異常。我想從這個角度也說明了立即載入機制。
好了,這就說到這裡,還是把對映檔案改為原來預設的吧(即去掉lazy="false"),看看還有其它什麼方法來立即載入。
2.使用NHibernateUtil實用類
NHibernate提供實用類(NHibernateUtil)不光光只是用來測試被關聯的物件集合是否已初始化,還有一個非常重要的功能就是可以強制初始化未初始化的相關聯的物件。有了這個功能,我們就可以修改資料訪問層中的方法,把上面使用Using強制清理關閉Session的方法中加上NHibernateUtil類提供Initialize方法來初始化Customer相關聯的Order物件集合。
public Customer EagerLoadUsingSessionAndNHibernateUtil(int customerId) { using (ISession _session = new SessionManager().GetSession()) { Customer customer= _session.Get<Customer>(customerId); NHibernateUtil.Initialize(customer.Orders); return customer; } }
我們編寫一個方法來測試一下:
[Test] public void EagerLoadUsingSessionAndNHibernateUtilTest() { Customer customer = _relation.EagerLoadUsingSessionAndNHibernateUtil(1); Assert.IsTrue(NHibernateUtil.IsInitialized(customer.Orders)); }
測試成功,這個結果同修改對映檔案一樣。
2.多對多關係例項
1.使用lazy="false"屬性
同理,使用lazy="false"屬性來設定立即載入行為,這時在持久化類中就不必為其公共方法、屬性和事件宣告為virtual屬性了,因為沒有使用延遲載入。不過在這裡我還是推薦大家使用NHibernate預設的延遲載入行為,原因很簡單,NHibernate延遲載入效能上可以提高很多,在特殊情況下使用下面的方法來立即載入。
這個例子同上面類似,這裡就不舉重複的例子了,大家自己測試下就可以了。
2.使用NHibernateUtil實用類
如果你需要獲得Order實體的相關聯物件可以使用NHibernateUtil類初始化關聯物件(把他們從資料庫取出來)。看看下面資料訪問層中的方法,使用NHibernateUtil類提供Initialize方法初始化相關聯的Customer和Product物件。
public DomainModel.Entities.Order EagerLoadOrderAggregateSessionAndNHibernateUtil(int orderId) { using (ISession _session = new SessionManager().GetSession()) { DomainModel.Entities.Order order = _session.Get<DomainModel.Entities.Order>(orderId); NHibernateUtil.Initialize(order.Customer); NHibernateUtil.Initialize(order.Products); return order; } }
測試上面的方法:
[Test] public void EagerLoadOrderAggregateSessionAndNHibernateUtilTest() { Order order = _relation.EagerLoadOrderAggregateSessionAndNHibernateUtil(2); Assert.IsTrue(NHibernateUtil.IsInitialized(order.Customer)); Assert.IsTrue(NHibernateUtil.IsInitialized(order.Products)); Assert.AreEqual(order.Products.Count, 2); }
看看NHibernate生成的SQL語句,真是多了,一對多關係,多對多關係的一次立即載入就生成了四條SQL語句,分別查詢了Order表,Customer表,OrderProduct表相關聯的Product。(Customer與Order一對多關係在這裡也立即載入了一次),這時記憶體中的內容都是這些關聯物件的值,你也不是每個物件都用到,何必要全部載入呢。
SELECT order0_.OrderId as OrderId6_0_, order0_.Version as Version6_0_, order0_.OrderDate as OrderDate6_0_, order0_.Customer as Customer6_0_ FROM [Order] order0_ WHERE order0_.OrderId=@p0; @p0 = '2' SELECT customer0_.CustomerId as CustomerId9_0_, customer0_.Version as Version9_0_, customer0_.Firstname as Firstname9_0_, customer0_.Lastname as Lastname9_0_ FROM Customer customer0_ WHERE customer0_.CustomerId=@p0; @p0 = '1' SELECT orders0_.Customer as Customer1_, orders0_.OrderId as OrderId1_, orders0_.OrderId as OrderId6_0_, orders0_.Version as Version6_0_, orders0_.OrderDate as OrderDate6_0_, orders0_.Customer as Customer6_0_ FROM [Order] orders0_ WHERE orders0_.Customer=@p0; @p0 = '1' SELECT products0_.[Order] as Order1_1_, products0_.Product as Product1_, product1_.ProductId as ProductId8_0_, product1_.Version as Version8_0_, product1_.Name as Name8_0_, product1_.Cost as Cost8_0_ FROM OrderProduct products0_ left outer join Product product1_ on products0_.Product=product1_.ProductId WHERE products0_.[Order]=@p0; @p0 = '2'
3.使用HQL抓取策略
使用HQL查詢方法也可以立即載入。HQL語句支援的連線型別為:inner join(內連線)、left outer join(左外連線)、right outer join(右外連線)、full join(全連線,不常用)。
“抓取fetch”連線允許僅僅使用一個選擇語句就將相關聯的物件隨著他們的父物件的初始化而被初始化,可以有效的代替了對映檔案中的外聯接與延遲屬性宣告。
幾點注意:
- fetch不與setMaxResults() 或setFirstResult()共用,因為這些操作是基於結果集的,而在預先抓取集合時可能包含重複的資料,也就是說無法預先知道精確的行數。
- fetch還不能與獨立的with條件一起使用。通過在一次查詢中fetch多個集合,可以製造出笛卡爾積,因此請多加註意。對多對多對映來說,同時join fetch多個集合角色可能在某些情況下給出並非預期的結果,也請小心。
- 使用full join fetch 與 right join fetch是沒有意義的。 如果你使用屬性級別的延遲獲取,在第一個查詢中可以使用 fetch all properties 來強制NHibernate立即取得那些原本需要延遲載入的屬性。
下面寫個簡單例子說明:
public DomainModel.Entities.Order EagerLoadOrderAggregateWithHQL(int orderId) { using (ISession _session = new SessionManager().GetSession()) { return _session.CreateQuery("from Order o"+ " left outer join fetch o.Products" + " inner join fetch o.Customer where o.OrderId=:orderId") .SetInt32("orderId", orderId) .UniqueResult<DomainModel.Entities.Order>(); } }
編寫測試用例測試上面的方法:驗證構建一個HQL查詢不僅載入Order,也載入了相關聯的Customer和Product物件。
[Test] public void EagerLoadOrderAggregateWithHQLTest() { Order order = _relation.EagerLoadOrderAggregateWithHQL(2); Assert.IsTrue(NHibernateUtil.IsInitialized(order.Customer)); Assert.IsTrue(NHibernateUtil.IsInitialized(order.Products)); Assert.AreEqual(order.Products.Count, 2); }
通過NHibernate生成SQL語句可以說明NHibernate可以一口氣立即載入Order和所有Order相關聯的Customer和Product物件。SQL語句生成如下:
select order0_.OrderId as OrderId6_0_, product2_.ProductId as ProductId8_1_, customer3_.CustomerId as CustomerId9_2_, order0_.Version as Version6_0_, order0_.OrderDate as OrderDate6_0_, order0_.Customer as Customer6_0_, product2_.Version as Version8_1_, product2_.Name as Name8_1_, product2_.Cost as Cost8_1_, customer3_.Version as Version9_2_, customer3_.Firstname as Firstname9_2_, customer3_.Lastname as Lastname9_2_, products1_.[Order] as Order1_0__, products1_.Product as Product0__ from [Order] order0_ left outer join OrderProduct products1_ on order0_.OrderId=products1_.[Order] left outer join Product product2_ on products1_.Product=product2_.ProductId inner join Customer customer3_ on order0_.Customer=customer3_.CustomerId where (order0_.OrderId=@p0 ); @p0 = '2' SELECT orders0_.Customer as Customer1_, orders0_.OrderId as OrderId1_, orders0_.OrderId as OrderId6_0_, orders0_.Version as Version6_0_, orders0_.OrderDate as OrderDate6_0_, orders0_.Customer as Customer6_0_ FROM [Order] orders0_ WHERE orders0_.Customer=@p0; @p0 = '1'
通過使用HQL抓取策略可以很好的在程式中編寫出自己想要的結果。
結語
通過這篇和上一篇我們初步認識了NHibernate中的載入機制,依次從一對多關係、多對多關係角度分析了NHibernate預設延遲載入和立即載入。這些僅僅是我在平時應用、學習中摸索出來的一點收穫,並非官方認可的東西,希望對你有所幫助。
本系列連結:NHibernate之旅系列文章導航
下次繼續分享NHibernate!