標籤:blog http io os 使用 ar strong 檔案 資料
本節內容
引入
通過上一篇的介紹,我們知道了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 [email protected]; @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 [email protected]; @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 [email protected]; @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 [email protected]; @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 [email protected]; @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_.ProductIdWHERE products0_.[Order][email protected]; @p0 = ‘2‘
3.使用HQL抓取策略
使用HQL查詢方法也可以立即載入。HQL語句支援的連線類型為:inner join(內串連)、left outer join(左外串連)、right outer join(右外串連)、full join(全串連,不常用)。
“抓取fetch”串連允許僅僅使用一個選擇語句就將相關聯的對象隨著他們的父物件的初始化而被初始化,可以有效代替了對應檔中的外聯結與延遲屬性聲明。
幾點注意:
- fetch不與setMaxResults() 或setFirstResult()共用,因為這些操作是基於結果集的,而在預先抓取集合時可能包含重複的資料,也就是說無法預Crowdsourced Security Testing道精確的行數。
- 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_.ProductIdinner join Customer customer3_ on order0_.Customer=customer3_.CustomerIdwhere ([email protected] ); @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 [email protected]; @p0 = ‘1‘
通過使用HQL抓取策略可以很好的在程式中編寫出自己想要的結果。
[轉]NHibernate之旅(13):初探立即載入機制