中,主要罗列了自己的一部分实践总结,其中包括:)
在NHibernate的mapping文件中所有关联实体和关联集合最好都保持默认的延迟加载即lazytrue以避免关联数据不必要的过量加载。2若确实需要获取关联数据则可编写join或dynamic fetching join的HQL或Criteria查询加载关联数据。3尽量避免在mapping文件中进行这些fetching strategy设置暂且称为抓取策略设置如fetchjoin关联抓取fetchsubselect子查询抓取batch-size20批量抓取等。因为在 mapping文件中的设置是global性质的设置会对整个系统产生全局性的影响更重要的是有些设置对Get、HQL和Criteria都产生影响而有些设置对Get、Criteria有影响却对HQL不影响。这些微妙的差异容易导致在系统中引入诡秘的bug同时还会增加开发的难度以及维护的负担。为了解决这个问题最简单有效的办法就是避免在mapping文件中进行这些设置。今天这篇文章主要介绍上面3中提到的HQL与Criteria对mapping文件中设置的抓取策略的差异现象即mapping文件中的fetchjoin抓取策略设置对Get、Criteria有影响对HQL不影响的现象。当一开始遇到这个现象时相信园子里的不少朋友会和我一样还以为是自己的代码写错了或是mapping文件设置错了呢经过调试确认代码和设置都没错之后甚感惊讶就怀疑是NHibernate的Bug。在网上搜索相关资料后才知道Hibernate(NHibernate)存在此现象My God我又没有先知先觉谁想得到呢气得真想把电脑给砸了算是被NHibernate耍了几个小时的调试时间从此以后在实际项目中就再也没在mapping文件中设置过任何fetching strategy。同样的设置对HQL和Criteria造成不同的表现我个人认为不是一个good idea。那么HibernateNHibernate为什么存在此现象肯定有其道理由于时间关系我没有深究有兴趣的朋友可以去查查相关资料。二、测试与验证过程接下来通过编写、测试一个简单的实例来验证这种差异并在文章最后提供该例子的下载该例子仅用于验证本文观点并不表示任何实际项目中的规范代码。1、开发环境先介绍一下这个例子的开发环境NHibernate 2.0DatabaseSQL Server 2005 根据你的实际情况附加数据库或者创建数据库数据表和测试数据并修改NHibernate配置文件中的数据数据库链接IDEVisual Studio 2008 English version测试工具NUnit2.4.82、场景有部门Department和员工Employee两个实体假定两者之间存在一对多的关系即一个部门拥有多个员工某个员工只属于一个部门。通过在Department的mapping文件中设置fetchjoin关联抓取使得在查找Department数据的同时获取关联集合数据Employee。3、目标验证fetchjoin设置对Get、Criteria查询有效对HQL查询无效。4、实例项目截图5、详细说明1测试数据库与测试数据Database目录下面是测试用的数据库文件NHibernatePractice2.mdf包含有两张数据表Department与Employee其中Employee表的DepartmentId是外键引用Department的主键。表中测试数据如下为了测试方便你可以直接附加数据库文件NHibernatePractice2.mdf到SQL Server2005否则也可以自己创建数据库然后手工或使用NHibernate的SchemaExport创建数据表、插入测试数据。2 引用的类库LibLib目录下面是引用的dll包括NHibernate.dll、nunit.framework.dll、Castle.DynamicProxy2.dll、Iesi.Collections.dll、log4net.dll。为项目添加这些引用。3hibernate.cfg.xml创建NHibernate配置文件注意该文件属性设置为“Copy always”配置如下Code4NHibernateHelper.cs接下来创建NHibernate辅助类用于获取NHibernate的session其中应用了Singleton模式Code5Domain Class在Domain目录下面创建两个实体类代码非常简单分别如下Department.cs:CodeEmployee.cs:Code6Mapping文件在Mappings目录下为Department与Employee分别创建映射文件注意文件属性设置为“Embedded Resource”Department.hbm.xml:CodeEmployee.hbm.xml:Code大家注意到我在Department.hbm.xml文件中将关联集合已经设置为fetchjoin。7测试类NHibernatePractice2Test.cs在Test目录下创建测试类NHibernatePractice2Test.cs代码如下Code由于例子本身就非常简单再加上已对代码进行了详细的注释所以我这里只做个简单的介绍。3个测试方法TestGet()、TestGetByCriteria()、TestGetByHQL()分别测试NHibernate的Get、Criteria和HQL获取数据。这里要注意的是每个测试方法在验证前都进行了transaction.Commit();效果就是关闭当前session使查询得到的实体数据处于detached状态。如果在获取Department数据时没有加载Employees集合数据这样当验证Assert.AreEqual(2, fromDb.Employees.Count);时会抛出NHibernate.LazyInitializationException异常。6、测试结果编译生成项目后在NUnit中进行测试其中TestGet()、TestGetByCriteria()测试成功TestGetByHQL()测试失败抛出NHibernate.LazyInitializationException异常failed to lazily initialize a collection, no session or session was closed得到的结果如下查看NHibernate为TestGet()、TestGetByCriteria()生成的sql两者功能一样在1条sql中对Department和Employee进行left outer join由此验证了mapping文件的fetchjoin设置影响Get与CriteriaCode查看NHibernate为TestGetByHQL()生成的sql没有进行join而只是查询Department由此验证了HQL忽略mapping文件中的fetchjoinNHibernate: select department0_.Id as Id0_, department0_.Name as Name0_from Department department0_where (Idp0 );p0 17、测试验证lazytrue对Get、Criteria、HQL都产生影响在Department.hbm.xml文件中删除关联集合的fetchjoin抓取策略设置改成立即加载lazytrue其他代码和文件都保持不变然后重新编译生成项目注意必须要重新编译生成项目发现3个测试方法都通过测试并且NHibernate为每个测试方法都生成相同功能sql每个测试方法都是2条sql第1条是获取Department由于设置了立即加载lazytrue就会立即执行第2条sql获取关联的EmployeeCode由此可见lazytrue对Get、Criteria、HQL都产生影响。8、其他抓取策略的验证除了fetchjoin、lazytrue还有fetchsubselect子查询抓取batch-size20批量抓取等抓取策略设置。由于篇幅受限以及时间关系就留给有兴趣的朋友自己动手验证了。不过如果你直接在Department.hbm.xml中将抓取策略修改成fetchsubselect或者batch-size20测试的结果将是3个测试方法都失败其中的缘由你自己去找我就给你个提示fetchsubselectbatch-size20在获取关联数据时都需要查取主数据的session不能关闭仍要保持打开状态。所以为了进行这类测试需要修改代码。三、网上资料上面的整个过程验证了HQL与Criteria对mapping文件中设置的抓取策略的差异接下来我们搜索一下网上关于此现象的资料其中提到的书是这本经典Java Persistence with Hibernate 大家可以看看电子版而且国内也有影印版。四、总结本文通过一个实例验证了HQL与Criteria对mapping文件中设置的抓取策略的差异现象具体表现在1、在mapping文件中的lazytrue设置global fetch plan对Get、Criteria、HQL都将产生影响2、在mapping文件中的fetchjoin设置global fetching strategy对Get、Criteria产生影响对HQL不影响。至于fetchsubselectbatch-size20我还未测试过所以不妄下结论有兴趣的朋友可以去试试。总之如果你不注意这些设置产生的诡秘差异你就会像我一开始一样会被NHibernate耍了。我发誓我不会再在mapping文件中进行这些设置除非不设置天就会塌下来呵呵。写得够多了现在才明白写技术blog真是一件既费时又辛苦的事情……不过下次还会继续……