本節內容
引入
立即加載
實例分析
1.一對多關系實例
2.多對多關系實例
結語
引入
通過上一篇的介紹,我們知道了NHibernate中默認的加載機制——延遲加載。其本質就是使用GoF23中代理模式實現,這節我們簡單分析NHibernate另一種加載機制——立即加載。我用一張圖片形象的展現立即加載機制。
立即加載
顧名思義,就是立刻加載相關聯對象集合,與延遲加載相反。我們可以使用三種方法來立即加載,分別是:可選的lazy屬性、NHibernate提供的實用類、HQL抓取策略。下面依次用實例分析其中的機制。
實例分析1.一對多關系實例
在一對多關系實例中,我們使用Customer對象與Order對象為例,在數據訪問層中依然使用上一篇的方法,這裡使用強制關閉Session的方法,為什麼使用Using強制釋放資源呢?我就是想利用這個來模擬Web應用程序中的Session機制。用這個分析比沒有Using釋放資源更有意義。
數據訪問層中方法:加載Customer對象並使用Using強制清理關閉Session
public Customer LazyLoadUsingSession(int customerId)
1.使用lazy="false"屬性
{
using (ISession _session = new SessionManager().GetSession())
{
return _session.Get<Customer>(customerId);
}
}
在上一篇我們一直沒有修改映射文件即一直默認是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_,
3.使用HQL抓取策略
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'
使用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默認延遲加載和立即加載。這些僅僅是我在平時應用、學習中摸索出來的一點收獲,並非官方認可的東西,希望對你有所幫助。