恋恋风尘

Coding is just my hobby……

博客园 首页 新随笔 联系 订阅 管理
      来博客园都有好几个年头了,本人一贯只看他人博文(不过近些年经得起看的文章是越来越少了),自己却从不写文章。一是才疏学浅,发“水文”怕误导大家;二是懒得动手;三是空闲时间也不多。不过今天晚上家门前工地通宵施工,机器的轰鸣声又一次响彻夜空,痛苦得实在无法入眠,写一篇我在博客园里的处女篇但愿能减轻一些痛苦吧……
      最近在使用NHibernate做系统,而且园子里玩NHibernate的人好像也多了几个,发现Jeffrey Zhao都NHibernate了,所以我也来凑个热闹,写一点自己的实践体会,以与园子里的NHibernate园友进行相互交流和学习。 李永京的NHibernate之旅系列文章导航通俗易懂,相信园子里大部分搞NHibernate的人多多少少都看过,NHibernate组里也有不少关于NHibernate讨论,这次就从NHibernate组里的几个话题展开说一下我的实践体会。

一、NHibernate 还有哪些不足?
      在这篇讨论中,组长和园友提了一些当前NHibernate的不足,我的补充是:

1. NHibernate不支持from子句的子查询(subquery)
  from子句中的子查询,又称谓derived table(派生表),SQL的写法大家都应该比较熟悉,举个最简单的例子(这里只说明from子查询的形式):
select Id from (select Id, Name from TableA group by Id, Name) as D;
  from子句的子查询使用方便、功能强大,经常应用于较复杂的查询场景。可惜的是目前最新版本的NHibernate2.1还不支持这种形式的子查询(当然了,你写包含from子查询的native sql让NHibernate执行的除外),将来的版本应该会支持。
  这里要说一下的是,NHibernate是支持where子句的子查询(关联、非关联子查询)的,具体见在线帮助文档11.11. Subqueries

2. 不支持select子查询
  select子查询的SQL写法:
select Employee.Id, Employee.Name, (select Department.Name from Department where Employee.DepartmentId=Department.Id) as DepName
from Employee
where Employee.Id=10;
  查了在线帮助文档和网上资料,NHibernate好像是不直接支持这种子查询的。但是可以在mapping文件中将子查询的列映射成property,或者使用Criteria的Projections.SqlProjection来实现类似功能。当然了,这类子查询我们往往可以使用join(inner join或者outer join)来实现同样的功能。

3.NHibernate2.0的Criteria不支持having子句
  我们经常需要对数据进行分组统计(group by +聚集函数),然后再对统计结果进行筛选(应用having子句)。NHibernate2.0的HQL支持having子句,但是NHibernate2.0的Criteria却不支持having子句(经过测试:可以调用相关API,但是执行没有效果)。因此,如果要在NHibernate2.0中使用having子句就必须得使用HQL而不能使用Criteria,可我是一直比较喜欢使用Criteria的,实在比较郁闷……(我为什么喜欢使用Criteria,也就是说Criteria与HQL相比有哪些优点,在以后的文章中再和大家详细讨论)。
  值得庆幸的是,根据最新的NHibernate2.1的发布文档说明(解压NHibernate2.1后便可看到releasenotes.txt文件,其中有一条Patch: * [NH-1280] - Adds HAVING support to CreateCriteria queries, Fixes parameter order bugs),Criteria可以支持having,不过我还没试过,具体的使用可以参见该文Adds HAVING support to CreateCriteria queries, Fixes parameter order bugs
4.不支持Property级别的lazy load
  目前只支持关联实体或者关联集合的延迟加载,不支持更细粒度的Property级别的延迟加载,而Linq to sql支持Property级别的延迟加载。

二、减少SQL语句的讨论  
  在这篇讨论中大家提出了减少NHibernate查询生成的SQL语句、避免n+1问题,以提高性能的方法,我的看法是:

1.立即加载(lazy=false)并不能在所有情况下都会减少SQL语句,同时还会带来过量读取数据的严重后果
  比如,在mapping文件中为实体AEntity设置了一个关联实体BEntity,并且将这个关联设置为lazy=false,这样当加载实体AEntity时(调用Get(id)),NHibernate就会自动加载实体BEntity。通过查看NHibernate生成的sql发现NHibernate生成的是2条sql,第一条sql获取的是AEntity,第二条获取的是BEntity,所以说在这种情况下,我的理解是,lazy=false并不会减少实际执行的sql语句,只是减少了程序员编写查询(sql)的数量,适当减轻了程序员的编程负担。
  立即加载的最大好处就是关联实体或集合由NHibernate自动加载,无需程序员手工加载。但是付出的代价则是有可能导致数据的过量读取,加大数据库、网络数据传输、应用服务器的负担,举例如下:
(1)多对一的关联实体立即加载:比如当前某个操作仅需获取AEntity而不需要获取BEntity的详细信息,由于立即加载,导致在加载AEntity的同时加载BEntity,如果BEntity还关联了CEntity而且也是立即加载,那么NHibernate同时还会加载CEntity,如果CEntity还关联了……
(2)一对多的关联集合立即加载:如果关联的数据是一个集合,并且集合数据很多(比如集合中有成千上万条数据),那么一次性立即加载整个关联集合数据的开销是显而易见的。
(3)类(class)级别的lazy=false:上面提到的两种情况是把lazy=false设置在关联级别上(细粒度),如果把lazy=false设置在整个class上(大粒度),那么情况将更加糟糕,这样当加载该类实体时会同时加载所有关联的实体和集合。

2.所有的关联实体和关联集合最好都保持默认的延迟加载
  由上面的分析可知,关联实体和集合最好保持默认的延迟加载,所以我在实际的开发中是不进行任何lazy=false设置的。如果要同时加载关联实体或集合,最有效的方法就是编写join和 dynamic fetching join的查询操作,这既可以减少所需执行的sql语句(join操作可以在一条语句中获取多个关联实体(关联数据表)或关联实体的properties(字段), 这是由join这类关系操作的本质决定的),同时按需获取数据避免数据的过量加载。

3. 普通join与 dynamic fetching join的区别
  刚开始使用NHibernate时,以为join与 dynamic fetching join是一回事,后来才明白两者的区别。我的理解,说得简单一点就是,dynamic fetching join会在一条sql语句中获取关联实体和/或关联集合数据,并把它(们)装配(marshal)成完整的persistent entity。而join会在一条sql语句中可以仅获取关联实体的某个property或某几个properties,而不一定是一个完整的persistent entity。具体join与 dynamic fetching join操作请参见在线文档。

4. 尽量避免在mapping文件中进行如下这些设置
  如:lazy="false"(立即加载), fetch="subselect"(子查询抓取),batch-size="20"(批量抓取),fetch="join"(关联抓取)等,因为在 mapping文件中的设置是global设置,会对整个系统产生全局性的影响。而且有些设置对Get、Load、HQL和Criteria都产生影响,而有些设置对HQL不影响对Criteria有影响。举个例子,当分别执行同样功能的HQL和Criteria时,却得到不同的查询结果时,你或许会感到非常惊讶;当对此问题进行了一天一夜的调试和冥思苦想之后,突然发现是由于mapping文件中的某个设置导致HQL和Criteria的差异时,你一定会气得想把电脑给砸了,并发誓永不再在mapping中设置这些东西!

5.使用NHibernate二级缓存是最好的选择吗
  使用NHIbernate二级缓存的缺点如下:
(1)二级缓存机制透明得让人觉得不可控;
(2)增加了测试、调试的复杂性;
(3)如果使用BULK或native sql对数据直接进行CUD操作,NHibernate是不会自动更新二级缓存的,导致二级缓存数据stale。为了避免stale,必须手工更新或同步缓存。
  我的建议是使用spring.net的缓存,在Service层(或BLL层)进行实体和查询结果数据的缓存,好处如下:
(1)spring.net的缓存既强大又灵活可配。
(2)如果系统有一天NHibernate被ADO.net、EF、linq to sql等其他机制替换了(尽管这种替换的可能性不大),spring.net缓存不受任何影响。
(3)不在DAL层而在service层(或BLL层)做缓存,使得DAL层的测试变得简单。

6.NHibernate+Spring.Net
  NHibernate再结合Spring.Net,省下大把大把的开发、维护时间,请给我一个不NHibernate+Spring.Net的理由?

7.只可惜.NET阵营中使用这两个优秀框架的人不多
  在国内外相关论坛问个问题答的人太少了。今年算是有了第一本关于Nhibernate的书籍《NHibernate in action》,Spring.net到目前为止还没看到任何书籍,而在线文档又极其简陋。

8.如果上天能给我一个再来一次机会的话,我还会选择.net吗
  本人并不想挑起这个老话题,但是java有许多关于hibernate、spring等优秀开源框架的经典书籍可以查阅,这是个不争的事实!

  天快亮了,而外面机器的轰鸣声依旧震耳欲聋……今天就说这些了,就花了4、5个小时,下次继续……
posted on 2009-09-01 01:00  恋恋风尘  阅读(10484)  评论(36编辑  收藏  举报