1、Nhibernate 全面分析NHibernate 是一个基于.Net 的针对关系型数据库的对象持久化类库.Nhibernate 来源于非常优秀的基于Java的Hibernate 关系型持久化工具. NHibernate源码分析之开篇: 计划和安排只从使用NHibernate以来,请被其强大的功能和使用的简洁所吸引. 为了进一步研究NHibernate,决定分析其源代码,如有感兴趣者,欢迎一起研究. 这里列出了将要分析的部分: 官方源码下载地址: NHibernate配置和持久对象映射文件 NHibernate配置有三种方式来存放nhibernate的配置 1, 作为单独的一节放在相应程序的配
2、置文件中,对于执行文件或类库为文件名称后加.config,对于则是放在web.config中.这种方式必须在配置文件的configSetions中声明nhibernate的配置节, 配置内容由Cfg.Environment类来读取,该类所有成员均为静态的,另外它还定义了配置中key值的常数.2. 放在一个单独的配置文件中,默认为hibernate.cfg.xml,使用时必须调用Cfg.Configuration.Config().如不是默认的配置文件名,还必须指明配置文件名称.这种方式最适合多数据库的情况,可以为每个数据库建立一个配置文件. 3. 手工在程序中加入,配置内容最后将加入到Cfg.
3、Configuration.Properties属性中,此属性为一IDictionary对象,并且为public,其余的就不用多话了吧. 下面对几个重要的key值说明一下: hibernate.connection.provider 连接提供者,取值必须是实现了IConnectionProvider接口的类的全名,当前版本只能取值NHibernate.Connection.DriverConnectionProvider; hibernate.connection.driver_class 数据驱动类,取值必须是实现了IDriver接口的类的全名,常用的选择有NHibernate.Driver
4、.SqlClientDriver, NHibernate.Driver.OleDbDriver等; hibernate.dialect 数据库方言,取值必须是继承之Dialect的类的全名,最常用的就是NHibernate.Dialect.MsSql2000Dialect了, 其它的没用过,不清楚能不能正常使用; hibernate.connection.connection_string 连接字符串,取值与driver_class对应即可; hibernate.show_sql 指明是否在log4net日志中显示sql语句,主要用于调试,取值为true或false; 完整的配置key列表请查
5、看Cfg.Environment类中的常数声明. 持久对象映射文件 nhibernate为我们提供了很多方式将持久对象映射文件加入到Cfg.Configuration类,下面将其一一列出: AddXmlFile:加入包括对象映射信息的文件; AddXmlString:加入包含映射信息的字符串; AddDocument:加入包含映射信息的Xmldocument; AddInputStream:加入包含映射信息的输入流; AddXmlReader:加入包含映射信息的XmlReader; AddResource:加入指定程序集的映射信息资源; AddClass:加入以类名指定的映射信息资源,映射文件
6、必须为classname.hbm.xml; AddAssembly:加入指定程序集名称的映射信息资源注意:如果映射信息为文件形式,包括加入到程序集资源的文件,那么文件名必须以.hbm.xml结尾. NHibernate架构分析uml图从图中可以看到,Session和SessionFactory是NHibernate的核心部分. SessionFactory维护到持久机制(数据库)的连接并对它们进行管理,同时还保存着所有持久对象的映射信息. SessionFactory由Configuration.BuildSessionFactory创建,这个对象一般使用Singleton模式. Sessio
7、n用于将对象持久化,支持数据库事务,另外Session还提供了强大的数据加载功能. Session由SessionFactory创建. 其它对象说明: IConnectionProvider: 连接提供者接口,负责与数据进行连接; Dialect: 数据库方言; CollectionPersister: 集合持久化类; IClassPersister: 类持久化接口,定义了基本的CRUD操作; TransactionFactory: 数据库事务工厂; IInterceptor: 拦截器接口,用于在操作执行时进行一些处理,典型的就是记录操作日志; NHibernate源码分析之一: 配置信息配置
8、信息用于指定NH以何种方式访问数据库, 根据这些配置信息, NH动态的创建数据访问对象并与数据库进行交互. 除了.net类库自带的Odbc, OleDb, OracleClient和SqlClient访问方式外, 在0.2版中, NH增加了用于访问MySQL和Firebird的访问方式, 这两种访问方式由第三方组件提供, mono的用户应该高兴了. :) NH的配置有两种存放方式存放在应用程序集的配置文件中, 对于Web应用程序则存放在Web.config中. 这种方式必须指定配置节的处理程序(类); 存放在一个单独的xml文件中, 使用这种方式我们必须在程序中显式的加载配置文件, 本文后面有
9、详细说明. 此方式有一个优点, 就是在多数据库的情况下, 可以用不同的配置文件与各个数据库进行对应. 配置内容先来看看配置内容, 下列是一个简单的配置例子: 连接提供者,取值必须是实现了IConnectionProvider接口的类的全名,当前版本只能取值NHibernate.Connection.DriverConnectionProvider. 数据库方言,取值必须是继承之Dialect的类的全名,最常用的就是NHibernate.Dialect.MsSql2000Dialect了吧, 谁让它是M$的了. 数据驱动类,取值必须是实现了IDriver接口的类的全名,常用的选择有NHibern
10、ate.Driver.SqlClientDriver, NHibernate.Driver.OleDbDriver等, 不过现在又多了ByteFXDataDriver(访问MySQL). 连接字符串,取值要与driver_class指定的数据驱动类对应. 配置节处理程序因为NH的配置信息为自定义配置节, 所以必须指定配置节处理程序, NH的配置内容采用key/value形式,这和预定义配置节appSettings是一样的,我们只要用.net内置的配置节处理程序就可以处理NH的配置内容了, 这个处理key/value形式的类就是NameValueSetionHandler. nhibernate
11、配置节的声明如下: 注意Version的值对于不同的.net framework版本取值也可能不一样. 在nh中, Environment类用于读取配置信息, 代码如下 /* Environment.cs - 65行 * static Environment() NameValueCollection props = System.Configuration.ConfigurationSettings.GetConfig(nhibernate) as NameValueCollection; if (props=null) return; foreach(string key in props
12、.Keys) propertieskey = propskey; 这是一个静态构造函数, 在静态成员首次调用时执行. 配置信息放在properties集合中. /* Environment.cs - 90行 * public static IDictionary Properties get IDictionary copy = new Hashtable(properties.Count); foreach(DictionaryEntry de in properties) copyde.Key = de.Value; return copy; Properties属性用于访问配置信息, 注
13、意这里并没有直接返回properties, 而是复制了一个集合用于返回. 曾有网友问为什么不是直接返回properties 可能的原因是如果返回properties(即引用)话, 那么配置信息将是共享的, 如果在程序中修改了properties, 那么将影响到其它地方. 另外Environment类中还定义了一些属性名称常数. 在程序中操作配置信息除了在配置文件中指定nh的配置信息外, nh还允许我们在程序中操作配置信息, 这对于一些敏感的数据,如数据库连接串,提供了一种安全的操作方法(可以在程序中加入连接串属性,而不用将其存储在配置文件中). Configuration类提供提供两个方法和一
14、个属性用于操作配置信息. /* Configuration.cs - 637行 * public Configuration AddProperties(IDictionary properties) foreach(DictionaryEntry de in properties) this.properties.Add(de.Key, de.Value); return this; 将一个数据字典对象加入到配置属性中. /* Configuration.cs - 646行 * public Configuration SetProperty(string name, string valu
15、e) propertiesname = value; return this; 设置指定的属性的值, name应使用Environment类中定义的那些属性名称常数. /* Configuration.cs - 625行 * public IDictionary Properties get return properties; set this.properties = value; 这个就不用多说的吧, 用dotNet的人都知道.有了Properties, 想干啥就干啥吧 :-) NHibernate源码分析之一续: 对象映射 1. 持久对象映射文件关于持久对象映射文件,这里就不多说了,可
16、参考nhibernate的例子和文档. 在nhibernate源代码的根目录里有一个nhibernate-mapping-2.0.xsd文档,这个文档是nhibernate用来对映射文件进行验证的,我们也可以借助相关软件用这个文档来验证映射文件的有效性. 2. 映射信息的读取通过Configuration类,可以用多种方式读取映射信息,一些以Add开头的方法就是用来加入映射信息的,这些方法最终将调用Add(XmlDocument doc). /* Configuration.cs * private Hashtable classes = new Hashtable(); classes集合用
17、于存放所有的持久对象映射信息, 它的Key为持久类的类型;Value为PermissionClass类的子类. private void Add(XmlDocument doc) try Binder.dialect = Dialect.Dialect.GetDialect(properties); Binder.BindRoot( doc, CreateMappings(); catch (MappingException me) log.Error(Could not compile the mapping document, me); throw me; / end try/catch
18、AddDocument方法调用Binder的静态方法BindRoot来绑定持久类映射信息.CreateMappings返回一个Mappings对象,此对象是一个简单封装了所有映射信息集合的类. 3. 建立对象映射信息 Binder类的BindRoot用于绑定映射信息中的所有映射内容. /* Binder.cs * public static void BindRoot(XmlDocument doc, Mappings model) / . foreach(XmlNode n in hmNode.SelectNodes(nsPrefix + :class, nsmgr) ) RootClass
19、 rootclass = new RootClass(); Binder.BindRootClass(n, rootclass, model); model.AddClass(rootclass); / . 遍历所有的类映射节点,然后调用BindRootClass来绑定类映射信息,最后将类映射信息加到集合中. 其中RootClass为PermissionClass的子类. public static void BindRootClass(XmlNode node, RootClass model, Mappings mappings) BindClass(node, model, mappin
20、gs); /TABLENAME XmlAttribute tableNameNode = node.Attributestable; string tableName = (tableNameNode=null) StringHelper.Unqualify( model.PersistentClazz.Name ) : tableNameNode.Value; XmlAttribute schemaNode = node.Attributesschema; string schema = schemaNode=null mappings.SchemaName : schemaNode.Val
21、ue; Table table = mappings.AddTable(schema, tableName); model.Table = table; / . PropertiesFromXML(node, model, mappings); BindRootClass首先调用BindClass绑定持久类映射信息,然后调用PropertiesFromXML来绑定类属性. public static void BindClass(XmlNode node, PersistentClass model, Mappings mapping) string className = node.Attr
22、ibutesname = null null : node.Attributesname.Value; / class try model.PersistentClazz = ReflectHelper.ClassForName(className); catch ( Exception cnfe ) throw new MappingException( persistent class not found, cnfe); / . BindClass通过反射来取得持久对象的类型. protected static void PropertiesFromXML(XmlNode node, Pe
23、rsistentClass model, Mappings mappings) string path = model.Name; Table table = model.Table; foreach(XmlNode subnode in node.ChildNodes) CollectionType collectType = CollectionType.CollectionTypeFromString(name); Value value = null; if (collectType!=null) value = new Value(table); BindValue(subnode,
24、 value, true); else if ( many-to-one.Equals(name) ) value = new ManyToOne(table); BindManyToOne(subnode, (ManyToOne) value, propertyName, true); else if ( any.Equals(name) ) value = new Any(table); BindAny(subnode, (Any) value, true); else if ( one-to-one.Equals(name) ) value = new OneToOne(table, m
25、odel.Identifier ); BindOneToOne(subnode, (OneToOne) value, true); else if ( property.Equals(name) ) value = new Value(table); BindValue(subnode, value, true, propertyName); else if ( component.Equals(name) ) value = new Component(model); BindComponent(subnode, (Component) value, reflectedClass, subp
26、ath, true, mappings); else if ( subclass.Equals(name) ) Subclass subclass = new Subclass(model); BindSubclass( subnode, subclass, mappings ); else if ( joined-subclass.Equals(name) ) Subclass subclass = new Subclass(model); BindJoinedSubclass( subnode, subclass, mappings); if ( value!=null) Property
27、 prop = new Property(value); BindProperty(subnode, prop, mappings); 遍历所有子节点,然后根据节点类型对进行绑定.(注: 部分内容已删除) 关于属性的映射以后有空再详细研究,只需要知道属性已加入到RootClass的Properties属性就行了. NHibernate源码分析之二: 会话工厂会话工厂是NHibernate中的关键类,它与数据库连接,数据库事务等进行交互,还存储着与所有持久对象类型关联的持久化对象,持久化类是持久化的关键,它实现基本的CRUD操作. 当用户需要持久操作时,由会话工厂创建一个会话供用户进行持久操作.
28、 1. 会话工厂的创建会话工厂由ISessionFactory接口实现,由Configuration的BuildSessionFactory方法创建,会话工厂应该使用Singleton模式. 如果要访问多个数据库,应建立多个会话工厂. /* Configuration.cs * public ISessionFactory BuildSessionFactory() / . Hashtable copy = new Hashtable(); foreach(DictionaryEntry de in properties) copy.Add(de.Key, de.Value); return
29、new SessionFactoryImpl(this, copy, interceptor); 其中SessionFactoryImpl为实现ISessionFactory的类,这个类的修饰为Internal. 2. 持久化类的创建持久化类用于对持久对象进行持久化操作,每一个持久对象类型都有一个与之关联的持久化对象. 持久化类继承自IClassPersister接口,这个接口定义了用于持久对象的CRUD操作. /* SessionFactoryImpl * private IDictionary classPersisters; 持久化对象集合,Key为持久对象的类型; private ID
30、ictionary classPersistersByName; 持久化对象集合,Key为持久对象的类名; public SessionFactoryImpl(Configuration cfg, IDictionary properties, IInterceptor interceptor) / . foreach(PersistentClass model in cfg.ClassMappings) System.Type persisterClass = model.Persister; IClassPersister cp; /TODO: H2.0.3 created a Persi
31、sterFactory if (persisterClass=null | persisterClass=typeof(EntityPersister) cp = new EntityPersister(model, this); else if (persisterClass=typeof(NormalizedEntityPersister) cp = new NormalizedEntityPersister(model, this); else cp = InstantiatePersister(persisterClass, model); classPersistersmodel.P
32、ersistentClazz = cp; classPersistersByNamemodel.Name = cp ; / . 在构造函数中遍历所有持久类映射信息,然后根据持久类的持久类型建立一个持久化对象,并将此对象加入到集合中. InstantiatePersister用于创建一个自定义的持久化对象,类名称由映射文件中Class节点的Persister属性指定,自定义持久化类必须实现IClassPersister接口. 3. 连接提供者连接提供者由IConnectionProvider接口实现,会话工厂通过连接提供者与持久机制(数据库等)进行交互,例如取得数据库连接等. /* Sessio
33、nFactoryImpl.cs * public SessionFactoryImpl(Configuration cfg, IDictionary properties, IInterceptor interceptor) / . connectionProvider = ConnectionProviderFactory.NewConnectionProvider(properties); / . 还是在构造函数中,连接提供者由连接提供者工厂根据配置属性来创建. /* ConnectionProviderFactory * public static IConnectionProvider
34、 NewConnectionProvider(IDictionary settings) IConnectionProvider connections = null; string providerClass = settingsCfg.Environment.ConnectionProvider as string; if (providerClass != null) try connections = (IConnectionProvider) Activator.CreateInstance(System.Type.GetType(providerClass); catch (Exc
35、eption e) throw new HibernateException(Could not instantiate connection provider: + providerClass); else throw new NotImplementedException(We have not implemented user supplied connections yet.); connections.Configure(settings); return connections; NewConnectionProvider方法通过配置中ConnectionProvider的值来创建
36、连接提供者.当前版本(v0.0.5)唯一可用的提供者只有DriverConnectionProvider类. / 部分内容等有空再补充. NHibernate源码分析之三: 会话与持久化操作会话是nhibernate中的主要接口,也是我们进行持久化操作和数据加载的主要接口,ISession在IClassPersister,ITransaction,ICriteria和IQuery之间起着协调者的作用. 会话对象通过调用会话工厂的OpenSession方法获得,OpenSession方法有一个参数interceptor,这是一个拦截器,由实现了IInterceptor接口的对象来完成,比较典型的
37、是对会话的操作进行日志记录. 1. 持久对象的状态持久对象的状态由EntityEntry类来维护. sealed internal class EntityEntry private LockMode _lockMode; private Status _status; private object _id; private object _loadedState; private object _deletedState; private bool _existsInDatabase; private object _version; / for convenience to save so
38、me lookups NonSerialized private IClassPersister _persister; private string _className; / . private IDictionary entitiesByKey; /key=Key, value=Object entitiesByKey集合保存当前会话中的所有持久对象. NonSerialized private IdentityMap entries;/key=Object, value=Entry entries集合维护当前会话中所有持久对象的状态,entries中的项目和entitiesByKey中
39、的项目是一一对应的. 2. 持久化操作当执行持久化操作时(Save/Update/Delete),除了少数情况外,持久化操作并没有立即执行(更新数据源),而是被记录下来,直到会话 Flush时才会实际更新到数据源,这样做的原因很容易理解,就是为了避免频烦的数据库连接操作.如果没有调用Flush而关闭了会话,当前会话中的持久对象将不会持久化! SessionImpl.cs中有三个集合用来记录要持久化的计划对象: NonSerialized private ArrayList insertions; 记录所有的ScheduledInsertion对象,ScheduledInsertion对象是通过
40、要Save的持久对象创建的,如果对象的标识必须从数据库获得(如Identity标识),那么并不会创建ScheduledInsertion对象,而是立即执行Save操作,原因很简单,因为必须取得 Identity标识; NonSerialized private ArrayList updates; 记录所有的ScheduledUpdate对象,ScheduledUpdate对象是通过要Update的持久对象创建的; NonSerialized private ArrayList deletions; 记录所有的ScheduledDeletion对象,ScheduledDeletion对象是通过
41、要Delete的持久对象创建的; 以上三个计划对象都从ScheduledEntityAction对象继承,而此对象实现了IExecutable接口,IExecutable接口的Execute方法用于执行执久化操作,此操作由Flush间接调用. 下面来看看Flush的代码: public void Flush() if (cascading0) throw new HibernateException( . ); FlushEverything(); Execute(); PostFlush(); Execute执行所有的计划对象. private void Execute() log.Debu
42、g(executing flush); try ExecuteAll( insertions ); insertions.Clear(); ExecuteAll( updates ); updates.Clear(); /. ExecuteAll( deletions ); deletions.Clear(); catch (Exception e) throw new ADOException(., e); 分别执行insert/update/delete计划. private void ExecuteAll(ICollection coll) foreach(IExecutable e i
43、n coll) executions.Add(e); e.Execute(); if ( batcher!=null ) batcher.ExecuteBatch(); 3. Save ISession有两种保存持久对象的方法,区别在于有没有指定对象Id(标识符). public object Save(object obj) if (obj=null) throw new NullReferenceException(attempted to save null); if ( !NHibernate.IsInitialized(obj) ) throw new PersistentObjec
44、tException(uninitialized proxy passed to save(); object theObj = UnproxyAndReassociate(obj); EntityEntry e = GetEntry(theObj); if ( e!=null ) if ( e.Status=Status.Deleted) Flush(); else log.Debug( object already associated with session ); return e.Id; object id; try id = GetPersister(theObj).Identif
45、ierGenerator.Generate(this, theObj); if( id = (object) IdentifierGeneratorFactory.ShortCircuitIndicator) return GetIdentifier(theObj); /TODO: yick! catch (Exception ex) throw new ADOException(Could not save object, ex); return DoSave(theObj, id); 先取得持久对象的状态,如为删除则flush;然后取得持久对象的id(标识符),最后调用DoSave方法.
46、有关持久对象的标识符请参考我的下一篇文章 持久对象标识符. public void Save(object obj, object id) if (obj=null) throw new NullReferenceException(attemted to insert null); if (id=null) throw new NullReferenceException(null identifier passed to insert(); if ( !NHibernate.IsInitialized(obj) ) throw new PersistentObjectException(u
47、ninitialized proxy passed to save(); object theObj = UnproxyAndReassociate(obj); EntityEntry e = GetEntry(theObj); if ( e!=null ) if ( e.Status=Status.Deleted ) Flush(); else if ( !id.Equals(e.Id) ) throw new PersistentObjectException(.); DoSave(theObj, id); 与前一个Save方法不同的是,不用取得持久对象的id,显然这个方法适用于对象标识符
48、已知的情况,这样会提高一些性能. private object DoSave(object obj, object id) IClassPersister persister = GetPersister(obj); Key key = null; bool identityCol; if (id=null) if ( persister.IsIdentifierAssignedByInsert ) identityCol = true; else throw new AssertionFailure(null id); else identityCol = false; if (!identityCol) / if the id is generated by the db, we assi