zoukankan      html  css  js  c++  java
  • NHibernate的认知,总结与陷阱

          使用NHibernate也有近三年了,从最初的2.1一直到现在的3.3.在使用过程中犯了很多错误,走了很多弯路.最近两天又研究了一下使用细节,觉得有必要将对NH的一些认知与研究成果记录下来,作为这一段时间内的学习总结.

          1.认识NH

          NH并不是数据访问层的灵丹妙药,其只有在以代码为中心,使用真正的面向对象/面向领域开发时才能发挥最大威力.它能让对象以最方便,最智能的方式持久化.它不是原生Ado.Net的替代者,更不是数据库相关技术的替代者.它严格遵守80/20原则,解决程序中80%的对象存储问题.在以数据为中心的场景,如查询,统计,报表等,还是使用原生Ado.Net为易.

          NH主要分为配置,映射,查询三大块.配置解决了数据库连接问题.映射解决了对象与表的关联问题.查询解决了数据获取问题.

          会使用NH的API离真正掌握NH还有很大距离.NH在使用中有很多陷阱,大都出现在查询中,会对性能有严重影响.比如著名的N + 1问题,笛卡尔积问题,一次请求一次连接问题等.这要求使用者不仅会使用API,还需要了解数据库相关知识,更需要了解代码背后执行的若干原理等.其它的一些陷阱则散布在配置,映射,缓存,对象状态中等.这些给于我们的启示是:初学者不要用,熟悉者谨慎用.要么不用,要么用好!

          2.相关资料

          这方面的资料首推园子里李永京大哥的两个NH系列文章:

          NHibernate之旅系列文章导航

          NHibernate3剖析

          前者的版本号是2.1,后者是3.0.认真看完并动手试验过后,就基本入门了.

          另外也有一些园友写了一些研究心得或某方面的专题介绍,个人觉得这些都是不可多得的好文章.

          NHibernate实践总结(一)

          NHibernate实践总结(二)

          NHibernate实践总结(三)

          NHibernate 3.x新功能实践(一)

          NHibernate 3.x新功能实践(二)

          NHibernate Pitfalls Index

          3.学习英语,努力学习英语

          不是我崇洋媚外,最新的技术资料与提问解答真心只有E文的.比如全世界最大的程序员社区StackOverflow.CSDN与它比起来真心爆弱了.我的非常多的关键的疑问都是在上面获得解答的.还有各个技术框架的官网与论坛.什么Extjs, Asp.Net MVC等等.真的,如果读的懂E文,95%的事真心就不难了.

          4.下载并在需要时查看源码

          当你对于一个错误百思不得其解时,查看源码就是最好的选择.不要怕难,不要怕烦.看上去最笨的方法往往是最近的捷径.

          5.关于Xml配置文件与配置硬编码

          这两个方面在配置,映射,查询中均有体现.在早期的版本中,一般都是使用Xml配置文件方式来使用NH,如数据库配置的Hibernate.cfg.xml,对象映射的Entity.hbm.xml等,而查询则使用类似于Sql的Hql.在最初的设想中,通过xml配置后的程序,在面临环境变更时可以做到0编译.如换库,调试/日志开关,各类选项开关等.但随着认识的深入,就发现其实配置太多反而适得其反:配置是不可编译和调试的;这就意味着如果报了错,只能人工一行一行的核对;配置是缺少智能提示的,这对使用者造成了使用困难;配置缺少合适的编辑工具与构建模型,一旦超过了一定的数据,就会让使用者迷失在字符串的海洋中.一句话:Xml配置并不是万能的,要学会适用而不是滥用.所以到3.0之后,乘着.Net Lambda表达式的东风,NH就推出了多种硬编码配置:如数据库配置就多了流配置与Lambda表达式配置,而映射则多了ConfORM,ByCode等,而查询更是推出了IQueryOver接口.将大部分的很少变化的设定直接写到程序中,如数据库类型,对象映射等,而将少量开关写入Xml配置文件中,如日志/调试开关等.

          本人目前使用的方式就是Lambda表达式配置 + ByCode映射配置 + IQueryOver查询接口 + Web.config配置.感觉不错~~~

          6.这些年我越过的那些陷阱

          a.延迟加载,抓取策略,N+1

          在上面NHibernate实践总结(二)这篇文章里就有一些研究,再加上其它园友的教训与实际体验,总结就是一句话:

          大部份情况下不要在配置文件里对它进行设置,而应该在程序中一次性的显式的获取你所需要的数据

          默认情况下NH的Lazy=True,Fetch=Select.它的出发点很好,加载尽可能少的数据以提高性能.但这其实有非常大的局限性.对象之间是互相关联,在实际使用中很少单独使用某一对象,而是多对象一起显示,修改,删除.比如最常见的显示正在购物的客户及其手头的订单,客户可能有多个,每个客户订单可能有多个.如果使用默认的加载方式,加载完客户集合后,会循环为每个客户单独加载自己订单,这就是N+1问题产生的根源.当然,你可以在配置文件中设定加载客户的同时一并加载各自的订单.但问题在于对于其它只需客户不需订单的使用场影,同时被加载的订单是多余的.所以,我得出了上面这句结论.虽然在程序中增加了若干行代码,但这是使用可以接受的代价,获取了最大的灵活性与最好的性能.

          b.查询笛卡尔积,奇怪的重复数据

          比如对象A,同时与对象B,对象C关联.界面要同时显示A,B,C的数据.假设A的一条,B有2条,C有3条.按照我上面的说法,显然是加载A的同时加载B与C.下面是使用IQueryOver的语法

    session.QueryOver<A>()
    .Fetch(B).Eager
    .Fetch(C).Eager
    .SingleOrDefault()

          很好,看上去语法没有错.即使是使用原生的Sql也会三表直接关联查询.但是这却不是性能最好的查询.因为在返回的记录集中,它会查出6条记录.其中A对象部分完全重复,B对象部分重复三次,C部分重复两次,但单看每一条记录,却又不是完全重复的.Ok,这就是传说中的笛卡尔积结果!其实还有更悲剧的结果.如果在NH映射中为B与C使用的是Bag,那么你就会发现在查出的结果中A对象有6个B对象与6个C对象!其中B重复三次C重复两次,与查询结果完全一致!什么,它不会自动去重吗?

          这个问题,我之前在看文档看例子没有任何在意,只有真真实实遇到了才有恍然大悟的感觉.NH给你提供了四种映射集合类型不是白给的,每一种都有它的适用范围.对于后面一个问题,有两种解决方法,要么在映射中使用Set,要么在程序多加一句:

    session.QueryOver<A>()
        .Fetch(B).Eager
        .Fetch(C).Eager
        .TransformUsing(Transformers.DistinctRootEntity)
        .SingleOrDefault()

          Eagerly fetch multiple collection: differences between QueryOver and Query

          对于前一个问题,只有改进查询方式,如下:

    var aFuture = session.QueryOver<A>()
        .Fetch(B).Eager
    .TransformUsing(Transformers.DistinctRootEntity) .FutureValue(); session.QueryOver
    <A>() .Fetch(C).Eager .TransformUsing(Transformers.DistinctRootEntity) .Future(); var result = aFuture.Value;

          NHibernate - Querying relationships at depth!

          Eagerly fetch multiple collection properties (using QueryOver/Linq)?

          Eagerly fetch multiple collection: differences between QueryOver and Query

          fetching multiple nested associations eagerly using nhibernate (and queryover)

          NHibernate lazy loading nested collections with futures to avoid N+1 problem

          NHibernate Pitfalls: Eager Loading Multiple Collections

         

          c.为什么NH自动生成的查询使用的都是Left Out Join

          说实话我一开始没有注意这个问题,后来在碰到其它问题,想将这个连接换成Inner时才发现这个情况.我自己想了半天不明所以,在网上查了半天才恍然大悟:

          为了保证所有满足条件的根对象被查出来.

          比如A对象,关联了B对象.有些A对象有多个B对象,有些则一个没有.当你联合查询所有A,B对象时你期望的结果是所有的A都能被查出,关联了B对象则B对象有值,反之为空.如果使用Inner关联,则只会查出所有关联了B对象的A对象.

          默认情况下这个Left Out Join连接是不可更改的.所以你如果真的想更换连接,则需要在程序设置.

          还有,只有使用了Left Out Join,Fetch设置为Join模式才会生效.而使用其它连接方式,强制使用Lazy=True,Fecth=Select,而不管你实际使用的是什么.这些都会导致N + 1问题.

          Inner or Right Outer Join in Nhibernate and Fluent Nhibernate on Many to Many collection

          d.多对多中奇怪的空记录

          这个问题只有使用Xml配置方式才会出现.因为默认提供的硬编码根本就不给你这个选项.当然,HN都是开源的,你自己是可以改滴!

          在多对多配置中,有一个where选项,如下:

    <many-to-many where="" class="" column=""></many-to-many>

          它想表达的意思是:你可以为另一个多的一方加上Sql条件.如,我们在界面上放置的删除按钮,通常都是逻辑删除,即在对象中加入一个IsDeleted字段,删除这个对象,就是将IsDeleted字段改为True.那么我在配置NH时,会在这个where中加入"IsDeleted = 0"来过滤这些已被删除的记录.假设A对象多对多关联了三个B对象,其中一个B对象的IsDeleted字段为True.在实际查询中,会很诡异的查出三条记录,但只有前两条有数据,第三条为空,而其所对应的ISet集合,居然也有三个元素,但前两个元素有值,第三个为null.

          我研究了其生成的查询语句,它将这个Where条件放在了连接条件中,而不是最终的Where子句中.这完全不符合我的本意啊!我想这也是为什么在新的ByCode配置中将其删除的原因.

          我现在的做法是,不在映射里配置,而是在程序中手工加上过滤条件.

          e.Many方法的Insert,Update与One方的Inverse

          这个问题也困扰了我很长时间,园子里有一篇文章写的很好,而我也就直接说结论了.

          在Many方法设置Insert=False, Update=False,NotFound=Ignore,在One方设置Inverse=True

          [Nhibernate] Inverse

          f.保存的各个方法的含义

          这个问题也困扰了我很长时间,当然,园子里仍然有一篇文章写的很好,而我也再次直接说结论了

          使用NH,大部分情况下严格遵守NH使用三步曲:加载,更新,保存.在大部分情况下,只需要使用Save方法.

          NHibernate的各种保存方式的区别 (save,persist,update,saveOrUpdte,merge,flush,lock)

          g.关联表使用独立主键

          这个也让我烦恼了很长时间,当然,这是我自己的问题,看文档不仔细.使用IdBag就可以解决这个问题!

          Nhibernate 3.0 cookbook学习笔记 集合

          h.一次请求一次连接

          我翻译的太土了,其E文名叫One Session Per Request.我发现还有很多人都在自己实现这个功能,我也曾经试图造过重复的轮子,但实际上NH早就自带相关特性了.具体请参看下面这篇文章:

          NHibernate Session Management in ASP.NET MVC

          《NHibernate One Session Per Request 简单实现》勘误

          i.可重写的日志

          从NH3开始,移除了对Log4Net的依赖,可以使用任意日志组件了.不过需要注意的是,NH中有很多个日志记录器,只有名为NHibernate.SQL的日志记录器才记录所有生成的Sql语句.且这个名字是不可改变的!切记!下面就是几篇参考的文章.

          Using NLog via Common.Logging with NHibernate

          How do NHibernate Profiler support NHibernate 3 logging

          Capture NHibernate generated SQL Query realtime at runtime

          Common.Logging.Elmah

          Simple logger for NHibernate 3

          j.扩展自己的ByCode

          这个问题是我在多对多映射中ByCode无法配置Where节所遇到的,本想通过继承而不是修改源代码来完成,但没有成功.参看下面两篇如何修改源代码的文章吧.

          How to implement .ChildWhere() mapping with many-to-many relation in NH 3.2

          Where() clause with many-to-many relation is missing (solution in description)

          但如上面所说的,这个Where节自身有缺陷,而我最终也放弃了扩展.

          匆忙间已挖不出更多的坑,也想不到更多的经验,只好搁笔于此.如果以后再想到相关内容,再自行补上.虽然用好NH不易,但这并不妨碍其成为.Net下最优秀的ORM框架.什么iBatis啊,EF啊,都是浮云.

          向为全世界做出卓越贡献的Hibernate框架与NHibernate框架的开发者们献上我最崇高的敬意!你们辛苦啦!

  • 相关阅读:
    angular 按下回车键触发事件
    vue 父组件与子组件的通信
    最近在开发一个文章聚合的工具
    Martinjingyu的开发环境
    个推push数据统计(爬虫)
    基于redis的订单号生成方案
    电商平台--Mysql主从搭建(2)
    Mysql主从搭建(1)
    mysql物理级别热备脚本
    外键查询及删除
  • 原文地址:https://www.cnblogs.com/ljzforever/p/2877144.html
Copyright © 2011-2022 走看看