本节内容
- 引入
- 马上载入
- 实例分析
- 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默认的延迟载入行为。原因非常easy,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默认延迟载入和马上载入。这些不过我在平时应用、学习中摸索出来的一点收获,并不是官方认可的东西。希望对你有所帮助。