只从使用NHibernate以来,请被其强大的功能和使用的简洁所吸引。
为了进一步研究NHibernate,决定分析其源代码,如有感兴趣者,欢迎一起研究。
这里列出了将要分析的部分:
1. NHibernate配置和持久对象映射文件
2. NHibernate架构分析(uml图)
3. NHibernate源码分析之一: 配置信息
4. NHibernate源码分析之一(续): 对象映射
5. NHibernate源码分析之二: 会话工厂
6. NHibernate源码分析之三: 会话与持久化操作
7. NHibernate源码分析之三(续): 数据持久化
8. NHibernate源码分析之四: 持久化操作与SQL语句
9. NHibernate源码分析之五: 对象标识
10. NHibernate源码分析之六: Criteria数据加载
11. NHibernate源码分析之七: HQL数据加载
12. NHibernate源码分析之八: 解析HQL
13. NHibernate源码分析之九: 事务处理
官方源码下载地址:
http://sourceforge.net/projects/nhibernate/
NHibernate配置
有三种方式来存放nhibernate的配置
1, 作为单独的一节放在相应程序的配置文件中,对于执行文件或类库为文件名称后加.config,对于asp.net则是放在web.config中。这种方式必须在配置文件的configSetions中声明nhibernate的配置节,
配置内容由Cfg.Environment类来读取,该类所有成员均为静态的,另外它还定义了配置中key值的常数。
2. 放在一个单独的配置文件中,默认为hibernate.cfg.xml,使用时必须调用Cfg.Configuration.Config()。如不是默认的配置文件名,还必须指明配置文件名称。这种方式最适合多数据库的情况,可以为每个数据库建立一个配置文件。
3. 手工在程序中加入,配置内容最后将加入到Cfg.Configuration.Properties属性中,此属性为一IDictionary对象,并且为public,其余的就不用多话了吧。
下面对几个重要的key值说明一下:
hibernate.connection.provider
连接提供者,取值必须是实现了IConnectionProvider接口的类的全名,当前版本只能取值NHibernate.Connection.DriverConnectionProvider;
hibernate.connection.driver_class
数据驱动类,取值必须是实现了IDriver接口的类的全名,常用的选择有NHibernate.Driver.SqlClientDriver, NHibernate.Driver.OleDbDriver等;
hibernate.dialect
数据库方言,取值必须是继承之Dialect的类的全名,最常用的就是NHibernate.Dialect.MsSql2000Dialect了, 其它的没用过,不清楚能不能正常使用;
hibernate.connection.connection_string
连接字符串,取值与driver_class对应即可;
hibernate.show_sql
指明是否在log4net日志中显示sql语句,主要用于调试,取值为true或false;
完整的配置key列表请查看Cfg.Environment类中的常数声明。
持久对象映射文件
nhibernate为我们提供了很多方式将持久对象映射文件加入到Cfg.Configuration类,下面将其一一列出:
AddXmlFile:加入包括对象映射信息的文件;
AddXmlString:加入包含映射信息的字符串;
AddDocument:加入包含映射信息的Xmldocument;
AddInputStream:加入包含映射信息的输入流;
AddXmlReader:加入包含映射信息的XmlReader;
AddResource:加入指定程序集的映射信息资源;
AddClass:加入以类名指定的映射信息资源,映射文件必须为classname.hbm.xml;
AddAssembly:加入指定程序集名称的映射信息资源
注意:如果映射信息为文件形式,包括加入到程序集资源的文件,那么文件名必须以.hbm.xml结尾。
从图中可以看到,Session和SessionFactory是NHibernate的核心部分。
SessionFactory维护到持久机制(数据库)的连接并对它们进行管理,同时还保存着所有持久对象的映射信息。
SessionFactory由Configuration.BuildSessionFactory创建,这个对象一般使用Singleton模式。
Session用于将对象持久化,支持数据库事务,另外Session还提供了强大的数据加载功能。
Session由SessionFactory创建。
其它对象说明:
IConnectionProvider: 连接提供者接口,负责与数据进行连接;
Dialect: 数据库方言;
CollectionPersister: 集合持久化类;
IClassPersister: 类持久化接口,定义了基本的CRUD操作;
TransactionFactory: 数据库事务工厂;
IInterceptor: 拦截器接口,用于在操作执行时进行一些处理,典型的就是记录操作日志;
配置信息用于指定NH以何种方式访问数据库, 根据这些配置信息, NH动态的创建数据访问对象并与数据库进行交互.除了.net类库自带的Odbc, OleDb, OracleClient和SqlClient访问方式外, 在0.2版中, NH增加了用于访问MySQL和Firebird的访问方式, 这两种访问方式由第三方组件提供, mono的用户应该高兴了. :)
NH的配置有两种存放方式
1. 存放在应用程序集的配置文件中, 对于Web应用程序则存放在Web.config中. 这种方式必须指定配置节的处理程序(类);
2. 存放在一个单独的xml文件中, 使用这种方式我们必须在程序中显式的加载配置文件, 本文后面有详细说明.此方式有一个优点, 就是在多数据库的情况下, 可以用不同的配置文件与各个数据库进行对应.
配置内容
先来看看配置内容, 下列是一个简单的配置例子:
<nhibernate>
连接提供者,取值必须是实现了IConnectionProvider接口的类的全名,当前版本只能取值NHibernate.Connection.DriverConnectionProvider.
<add
key="hibernate.connection.provider"
value="NHibernate.Connection.DriverConnectionProvider"
/>
数据库方言,取值必须是继承之Dialect的类的全名,最常用的就是NHibernate.Dialect.MsSql2000Dialect了吧,谁让它是M$的了.
<add
key="hibernate.dialect"
value="NHibernate.Dialect.MsSql2000Dialect"
/>
数据驱动类,取值必须是实现了IDriver接口的类的全名,常用的选择有NHibernate.Driver.SqlClientDriver, NHibernate.Driver.OleDbDriver等, 不过现在又多了ByteFXDataDriver(访问MySQL).
<add
key="hibernate.connection.driver_class"
value="NHibernate.Driver.SqlClientDriver"
/>
连接字符串,取值要与driver_class指定的数据驱动类对应.
<add
key="hibernate.connection.connection_string"
value="Server=localhost;initialecatalog=nhibernate;Integrated Security=SSPI"
/>
</nhibernate>
配置节处理程序
因为NH的配置信息为自定义配置节, 所以必须指定配置节处理程序, NH的配置内容采用key/value形式,这和预定义配置节appSettings是一样的,我们只要用.net内置的配置节处理程序就可以处理NH的配置内容了, 这个处理key/value形式的类就是NameValueSetionHandler.
nhibernate配置节的声明如下:
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</configSections>
注意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.Keys)
{
properties[key] = props[key];
}
}
这是一个静态构造函数, 在静态成员首次调用时执行.
配置信息放在properties集合中.
//*** Environment.cs - 90行 ***
public static IDictionary Properties
{
get
{
IDictionary copy = new Hashtable(properties.Count);
foreach(DictionaryEntry de in properties)
{
copy[de.Key] = de.Value;
}
return copy;
}
}
Properties属性用于访问配置信息, 注意这里并没有直接返回properties, 而是复制了一个集合用于返回.
曾有网友问为什么不是直接返回properties? 可能的原因是如果返回properties(即引用)话, 那么配置信息将是共享的, 如果在程序中修改了properties, 那么将影响到其它地方.
另外Environment类中还定义了一些属性名称常数.
在程序中操作配置信息
除了在配置文件中指定nh的配置信息外, nh还允许我们在程序中操作配置信息, 这对于一些敏感的数据,如数据库连接串,提供了一种安全的操作方法(可以在程序中加入连接串属性,而不用将其存储在配置文件中).
Configuration类提供提供两个方法和一个属性用于操作配置信息.
//*** 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 value)
{
properties[name] = value;
return this;
}
设置指定的属性的值, name应使用Environment类中定义的那些属性名称常数.
//*** Configuration.cs - 625行 ***
public IDictionary Properties
{
get { return properties; }
set { this.properties = value; }
}
这个就不用多说的吧, 用dotNet的人都知道.有了Properties, 想干啥就干啥吧 :-)
1. 持久对象映射文件
关于持久对象映射文件,这里就不多说了,可参考nhibernate的例子和文档。
在nhibernate源代码的根目录里有一个nhibernate-mapping-2.0.xsd文档,这个文档是nhibernate用来对映射文件进行验证的,我们也可以借助相关软件用这个文档来验证映射文件的有效性。
2. 映射信息的读取
通过Configuration类,可以用多种方式读取映射信息,一些以Add开头的方法就是用来加入映射信息的,这些方法最终将调用Add(XmlDocument doc)。
//** Configuration.cs **
private Hashtable classes = new Hashtable();
classes集合用于存放所有的持久对象映射信息,
它的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
}
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 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, mappings);
//TABLENAME
XmlAttribute tableNameNode = node.Attributes["table"];
string tableName = (tableNameNode==null)
? StringHelper.Unqualify( model.PersistentClazz.Name )
: tableNameNode.Value;
XmlAttribute schemaNode = node.Attributes["schema"];
string schema = schemaNode==null ? mappings.SchemaName : schemaNode.Value;
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.Attributes["name"] == null ? null : node.Attributes["name"].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, PersistentClass 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, 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, model.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, subpath, 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 prop = new Property(value);
BindProperty(subnode, prop, mappings);
}
}
}
遍历所有子节点,然后根据节点类型对进行绑定。(注: 部分内容已删除)
关于属性的映射以后有空再详细研究,只需要知道属性已加入到RootClass的Properties属性就行了。
会话工厂是NHibernate中的关键类,它与数据库连接、数据库事务等进行交互,还存储着与所有持久对象类型关联的持久化对象,持久化类是持久化的关键,它实现基本的CRUD操作。
当用户需要持久操作时,由会话工厂创建一个会话供用户进行持久操作。
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 new SessionFactoryImpl(this, copy, interceptor);
}
其中SessionFactoryImpl为实现ISessionFactory的类,这个类的修饰为Internal。
2. 持久化类的创建
持久化类用于对持久对象进行持久化操作,每一个持久对象类型都有一个与之关联的持久化对象。
持久化类继承自IClassPersister接口,这个接口定义了用于持久对象的CRUD操作。
//*** SessionFactoryImpl ***
private IDictionary classPersisters;
持久化对象集合,Key为持久对象的类型;
private IDictionary 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 PersisterFactory
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);
}
classPersisters[model.PersistentClazz] = cp;
classPersistersByName[model.Name] = cp ;
}
// ...
}
在构造函数中遍历所有持久类映射信息,然后根据持久类的持久类型建立一个持久化对象,并将此对象加入到集合中。
InstantiatePersister用于创建一个自定义的持久化对象,类名称由映射文件中Class节点的Persister属性指定,自定义持久化类必须实现IClassPersister接口.
3. 连接提供者
连接提供者由IConnectionProvider接口实现,会话工厂通过连接提供者与持久机制(数据库等)进行交互,例如取得数据库连接等。
//*** SessionFactoryImpl.cs ***
public SessionFactoryImpl(Configuration cfg, IDictionary properties, IInterceptor interceptor)
{
// ...
connectionProvider = ConnectionProviderFactory.NewConnectionProvider(properties);
// ...
}
还是在构造函数中,连接提供者由连接提供者工厂根据配置属性来创建。
//*** ConnectionProviderFactory ***
public static IConnectionProvider NewConnectionProvider(IDictionary settings)
{
IConnectionProvider connections = null;
string providerClass = settings[Cfg.Environment.ConnectionProvider] as string;
if (providerClass != null)
{
try
{
connections = (IConnectionProvider) Activator.CreateInstance(System.Type.GetType(providerClass));
}
catch (Exception 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的值来创建连接提供者。当前版本(v0.0.5)唯一可用的提供者只有DriverConnectionProvider类。
// 部分内容等有空再补充。
会话是nhibernate中的主要接口,也是我们进行持久化操作和数据加载的主要接口,ISession在IClassPersister、ITransaction、ICriteria和IQuery之间起着协调者的作用。
会话对象通过调用会话工厂的OpenSession方法获得,OpenSession方法有一个参数interceptor,这是一个拦截器,由实现了IInterceptor接口的对象来完成,比较典型的是对会话的操作进行日志记录。
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 some 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中的项目是一一对应的。
2. 持久化操作
当执行持久化操作时(Save/Update/Delete),除了少数情况外,持久化操作并没有立即执行(更新数据源),而是被记录下来,直到会话Flush时才会实际更新到数据源,这样做的原因很容易理解,就是为了避免频烦的数据库连接操作。如果没有调用Flush而关闭了会话,当前会话中的持久对象将不会持久化!
SessionImpl.cs中有三个集合用来记录要持久化的计划对象:
[NonSerialized] private ArrayList insertions;
记录所有的ScheduledInsertion对象,ScheduledInsertion对象是通过要Save的持久对象创建的,如果对象的标识必须从数据库获得(如Identity标识),那么并不会创建ScheduledInsertion对象,而是立即执行Save操作,原因很简单,因为必须取得Identity标识;
[NonSerialized] private ArrayList updates;
记录所有的ScheduledUpdate对象,ScheduledUpdate对象是通过要Update的持久对象创建的;
[NonSerialized] private ArrayList deletions;
记录所有的ScheduledDeletion对象,ScheduledDeletion对象是通过要Delete的持久对象创建的;
以上三个计划对象都从ScheduledEntityAction对象继承,而此对象实现了IExecutable接口,IExecutable接口的Execute方法用于执行执久化操作,此操作由Flush间接调用。
下面来看看Flush的代码:
public void Flush()
{
if (cascading>0) throw new HibernateException( "..." );
FlushEverything();
Execute();
PostFlush();
}
Execute执行所有的计划对象。
private void Execute()
{
log.Debug("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 in 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 PersistentObjectException("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).IdentifierGenerator.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方法。
有关持久对象的标识符请参考我的下一篇文章 《持久对象标识符》。
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("uninitialized 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,显然这个方法适用于对象标识符已知的情况,这样会提高一些性能。
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 assign the key later
key = new Key(id, persister);
object old = GetEntity(key);
if (old!=null)
{
if ( GetEntry(old).Status==Status.Deleted)
{
Flush();
}
else
{
throw new HibernateException( "...") );
}
}
persister.SetIdentifier(obj, id);
}
// ...
// Put a placeholder in entries, ...
AddEntry(obj, Status.Saving, null, id, null, LockMode.Write, identityCol, persister);
// cascade-save to many-to-one BEFORE the parent is saved
// ...
// set property values ...
if (identityCol)
{
try
{
id = persister.Insert(values, obj, this);
}
catch (Exception e)
{
throw new ADOException("Could not insert", e);
}
key = new Key(id, persister);
if ( GetEntity(key) != null )
throw new HibernateException("...");
persister.SetIdentifier(obj, id);
}
AddEntity(key, obj);
AddEntry(obj, Status.Loaded, values, id, Versioning.GetVersion(values, persister), LockMode.Write, identityCol, persister);
if (!identityCol) insertions.Add( new ScheduledInsertion( id, values, obj, persister, this ) );
// cascade-save to collections AFTER the collection owner was saved
// ...
return id;
}
DoSave方法首先判断id是否为赋值的(assign),然后将持久对象加入到当前会话的集合中。
如果id为identity类型的,则直接调用持久对象的持久化类来插入数据,否则将持久对象加入到insertions集合中,直到调用Flush时才插入数据。
4. Update
Update的处理基本上同Create是类似的,但值得注意的是Update方法并没有将持久对象加入到updates集合中,而是在执行Flush的时候通过判断持久对象的属性来决定持久对象是否需要Update。
5. Delete
Delete的处理比较复杂一些,包括处理集合和级联,这里就不贴出代码了。关于集合和级联处理我会在后续文章中进行分析。
当持久化对象时,显然必须存在把记录的值赋值到对象属性和取得对象属性的值用于持久化操作,对于更新操作,还需要检查对象的值是否已发生变化,即是否为Dirty,这些操作都是由对象的持久化类来完成的。有关持久化类可参考《会话和持久化操作》一文。
下面对NH的源码进行分析,以了解NH中数据加载和更新的过程。
一、持久对象加载
先来想像一下对象的加载过程(Load).
1. 根据对象Id从数据库取得记录;
2. 使用默认的构造函数构造一个对象;
3. 把记录的值存储在一个地方,用于在保存时进行比较;
4. 把记录的值赋值给对象的属性。
在本文的前篇中已经分析了对象加载过程的前半部分,这里仅对后半部分进行分析。
//*** EntityLoader.cs 34行 ***
public object Load(ISessionImplementor session, object id, object obj)
{
IList list = LoadEntity(session, new object[] { id }, idType, obj, id, false);
if (list.Count==1)
{
return list[0];
}
else if (list.Count==0)
{
return null;
}
else
{
throw new HibernateException( "..." );
}
}
当使用Session.Load加载对象时,最后将调用对象持久化类的Load方法。
//*** Loader.cs 745行 ***
protected IList LoadEntity( ISessionImplementor session,
object[] values, IType[] types, object optionalObject,
object optionalID, bool returnProxies)
{
return DoFind(session, values, types, optionalObject,
optionalID, null, null, returnProxies, null, null, null);
}
直接调用DoFind。
private IList DoFind(
ISessionImplementor session, object[] values,
IType[] types, object optionalObject,
object optionalID, PersistentCollection optionalCollection,
object optionalCollectionOwner, bool returnProxies,
RowSelection selection, IDictionary namedParams,
IDictionary lockModes)
{
// 取得要加载的持久对象类型,有些查询只取某些属性的值,这时Persisters是空集合。
ILoadable[] persisters = Persisters;
int cols = persisters.Length;
bool returnsEntities = cols > 0; // 判断是否要返回实体(持久对象)
ArrayList hydratedObjects = returnsEntities ? new ArrayList() : null;
Key optionalObjectKey;
if (optionalObject!=null)
{
optionalObjectKey = new Key(optionalID, session.GetPersister(optionalObject) );
}
else
{
optionalObjectKey = null;
}
IList results = new ArrayList();
IDbCommand st = null;
// 解析查询字符串并生成IDbCommand.
st = PrepareCommand(
ApplyLocks(SqlString, lockModes, session.Factory.Dialect),
values, types, namedParams, selection, false, session);
IDataReader rs = GetResultSet(st, selection, session);
try
{
Key[] keys = new Key[cols];
// 开始处理结果集.
int count;
for ( count=0; count<maxRows && rs.Read(); count++)
{
for (int i=0; i<cols; i++)
{
keys[i] = GetKeyFromResultSet(i, persisters[i],
(i==cols-1) ? optionalID : null, rs, session );
}
// 取得结果行集并进行处理。
object[] row = GetRow(rs, persisters, suffixes, keys, optionalObject,
optionalObjectKey, lockModeArray, hydratedObjects, session);
results.Add(GetResultColumnOrRow(row, rs, session));
}
}
catch (Exception e)
{
throw e;
}
finally
{
// do something.
}
// 如果有返回实体,则初始化实体(即初始化持久对象)。
if(returnsEntities)
{
int hydratedObjectsSize = hydratedObjects.Count;
for (int i = 0; i < hydratedObjectsSize; i++) session.InitializeEntity(hydratedObjects[i]);
}
return results;
}
DoFind的是对象加载的最终方法,所有的数据加载,包括HQL,Criteria查询,最后都将调用此方法,代码中省略了处理集合的部分。
在此方法中,首先取得结果集,然后对结果进行处理(构造对象和取得列值),最后初始化实体(设置属性值)。
//*** Loader.cs 318行 ***
private object[] GetRow( IDataReader rs, ILoadable[] persisters,
string[] suffixes, Key[] keys, object optionalObject,
Key optionalObjectKey, LockMode[] lockModes, IList hydratedObjects,
ISessionImplementor session)
{
int cols = persisters.Length;
object[] rowResults = new object[cols];
for (int i=0; i<cols; i++)
{
object obj = null;
Key key = keys[i];
if (keys[i]==null)
{
// do nothing - used to have hydrate[i] = false;
}
else
{
//If the object is already loaded, return the loaded one
obj = session.GetEntity(key);
if (obj!=null)
{
//its already loaded so dont need to hydrate it
InstanceAlreadyLoaded(rs, i, persisters[i], suffixes[i], key, obj,
lockModes[i], session);
}
else
{
obj = InstanceNotYetLoaded(rs, i, persisters[i], suffixes[i], key,
lockModes[i], optionalObjectKey, optionalObject, hydratedObjects, session);
}
}
rowResults[i] = obj;
}
return rowResults;
}
检查需要返回的对象数,然后循环进行处理,这里可以看出NH设计的是非常棒的,当对象已经加载过时,就不用进行hydrate了, hydrate不好译(我e文不太好), 它的功能是把记录集的值转化成一个与对象属性对应的object数组。
//*** Loader.cs 365行 ***
private object InstanceNotYetLoaded(IDataReader dr, int i, ILoadable persister,
string suffix, Key key, LockMode lockMode, Key optionalObjectKey,
object optionalObject, IList hydratedObjects, ISessionImplementor session)
{
object obj;
// 取得对象的实际类型。
System.Type instanceClass = GetInstanceClass(dr, i, persister, suffix,
key.Identifier, session);
if(optionalObjectKey!=null && key.Equals(optionalObjectKey))
{
obj = optionalObject;
}
else
{
obj = session.Instantiate(instanceClass, key.Identifier);
}
// need to hydrate it
LockMode acquiredLockMode = lockMode==LockMode.None ? LockMode.Read : lockMode;
LoadFromResultSet(dr, i, obj, key, suffix, acquiredLockMode, persister, session);
// materialize associations (and initialize the object) later
hydratedObjects.Add(obj);
return obj;
}
这里有两处很重要的代码,印证了我们之前的猜想,一个是session.Instantiate方法,这个用于构造对象;另一个是LoadFromResultSet,它将结果集转换为属性集对象的object数组。
//*** Session.cs 1769行 ***
public object Instantiate(System.Type clazz, object id)
{
return Instantiate( factory.GetPersister(clazz), id );
}
public object Instantiate(IClassPersister persister, object id)
{
object result = interceptor.Instantiate( persister.MappedClass, id );
if (result==null) result = persister.Instantiate(id);
return result;
}
在实例化之前,NH把控制权交给了interceptor(拦截器),我们可以在拦截器中实现实例化对象并返回,如果返回不为null, NH将不再进行实例化;如果为空,则由对象的持久化类构造对象。
//*** AbstractEntityPersister 227行 ***
public virtual object Instantiate(object id)
{
if (hasEmbeddedIdentifier && id.GetType()==mappedClass)
{
return id;
}
else
{
if (abstractClass) throw new HibernateException("...");
try
{
return constructor.Invoke(null);
}
catch (Exception e)
{
throw new InstantiationException("...");
}
}
}
constructor是一个ConstructorInfo对象,对反射熟悉的朋友应该知道这是一个构造函数对象,这个constructor在AbstractEntityPersister的构造函数中赋值,它是一个不带任何参数的构造函数对象。
//*** Loader.cs 427行 ***
private void LoadFromResultSet(IDataReader rs, int i, object obj, Key key,
string suffix, LockMode lockMode, ILoadable rootPersister,
ISessionImplementor session)
{
session.AddUninitializedEntity(key, obj, lockMode);
// Get the persister for the subclass
ILoadable persister = (ILoadable) session.GetPersister(obj);
string[][] cols = persister==rootPersister ?
suffixedPropertyColumns[i] :
GetPropertyAliases(suffix, persister);
object id = key.Identifier;
object[] values = Hydrate(rs, id, obj, persister, session, cols);
session.PostHydrate(persister, id, values, obj, lockMode);
}
先将未初始化的对象加入到session的实体集合中,然后从行集中取得与属性对应的值, 最后将值传递给session。
// *** Loader.cs 492行 ***
private object[] Hydrate(IDataReader rs, object id, object obj, ILoadable persister, ISessionImplementor session, string[][] suffixedPropertyColumns)
{
IType[] types = persister.PropertyTypes;
object[] values = new object[ types.Length ];
for (int i=0; i<types.Length; i++)
{
values[i] = types[i].Hydrate( rs, suffixedPropertyColumns[i], session, obj);
}
return values;
}
循环处理所有的属性类型,IType.Hydrate用于从行集中取得与属性对应的值。
// *** Loader.cs 1930行 ***
public void PostHydrate(IClassPersister persister, object id,
object[] values, object obj, LockMode lockMode)
{
persister.SetIdentifier(obj, id);
object version = Versioning.GetVersion(values, persister);
AddEntry(obj, Status.Loaded, values, id, version, lockMode, true, persister);
}
调整session实体集合中对象的状态。关于实体集合,请参考本文的前篇《会话和持久化操作》一文
现在对象也构造了,与属性对应的值也获得了,就差最后一步了,把值赋给对象的属性,
// *** Session.cs 2257行 ***
public void InitializeEntity(object obj)
{
EntityEntry e = GetEntry(obj);
IClassPersister persister = e.Persister;
object id = e.Id;
object[] hydratedState = e.LoadedState;
IType[] types = persister.PropertyTypes;
interceptor.OnLoad( obj, id, hydratedState, persister.PropertyNames, types );
for ( int i=0; i<hydratedState.Length; i++ )
{
hydratedState[i] = types[i].ResolveIdentifier( hydratedState[i], this, obj );
}
persister.SetPropertyValues(obj, hydratedState);
TypeFactory.DeepCopy(hydratedState, persister.PropertyTypes,
persister.PropertyUpdateability, hydratedState);
if ( persister.HasCache )
{
persister.Cache.Put( id, new CacheEntry( obj, persister, this), timestamp );
}
// reentrantCallback=true;
if ( persister.ImplementsLifecycle ) ((ILifecycle) obj).OnLoad(this, id);
}
首先调用拦截器的OnLoad方法,可以在拦截器做一些处理,例如修改hydratedState的值(字段值安全也许用的上),
然后通过对象的持久化类设置属性值(SetPropertyValues);
DeepCopy用于对值进行深拷贝,对C++熟悉的朋友应该对深拷贝不会陌生吧,简单点说,就是要将一些会共用的值进行分离;
如果对象支持Cache,就将对象放入缓存;
最后如果对象自行管理生命周期,则调用ILifecycle.OnLoad方法。
// *** AbstractEntityPersister.cs 130行 ***
public virtual void SetPropertyValues(object obj, object[] values)
{
for (int j=0; j<hydrateSpan; j++)
{
Setters[j].Set(obj, values[j]);
}
}
Setters是一个PropertyInfo对象数组,它保存着所有映射属性的PropertyInfo对象, 通过循环对所有的属性进行赋值。
现在我们就完成了数据加载的过程,得到与数据记录对应的对象,与我们所想像的过程是差不多的,当然这些细节的处理是很复杂的。
二、持久对象更新和dirty状态.
更新的步骤:
1. 取得对象的属性值;
2. 取得会话实体集合中的初始状态值;
3. 比较1和2的值,看是否发生改动,如发生改动,则设置dirty为true;
4. 根据dirty的状态决定是否更新对象;
5. 如有更新对象,更改初始状态值为新的属性值。
在本文的前篇中已经分析了对象更新过程的前半部分,这里仅对后半部分进行分析。
在前篇文章中我们已经指出,当update对象时,对象并不会立即更新,而是放入一个集合,只有调用了flush方法,才会执行真正的更新操作。有些操作将会自动的调用flush方法,如提交事务。
// *** Session.cs 2328行 ***
public void Flush()
{
FlushEverything();
Execute();
PostFlush();
}
// *** Session.cs 2341行 ***
private void FlushEverything()
{
interceptor.PreFlush( entitiesByKey.Values );
PreFlushEntities();
PreFlushCollections();
FlushEntities();
FlushCollections();
}
调用拦截器的PreFlush方法,传递会话中的所有实体。
// *** Session.cs 2469行 ***
private void FlushEntities()
{
ICollection iterSafeCollection = IdentityMap.ConcurrentEntries(entries);
foreach(DictionaryEntry me in iterSafeCollection)
{
EntityEntry entry = (EntityEntry) me.Value;
Status status = entry.Status;
if (status != Status.Loading && status != Status.Gone)
{
object obj = me.Key;
IClassPersister persister = entry.Persister;
object[] values;
if ( status==Status.Deleted)
{
values = entry.DeletedState;
}
else
{
values = persister.GetPropertyValues(obj);
}
IType[] types = persister.PropertyTypes;
bool substitute = Wrap(values, types);
bool cannotDirtyCheck;
bool interceptorHandledDirtyCheck;
int[] dirtyProperties = interceptor.FindDirty(obj, entry.Id, values,
entry.LoadedState, persister.PropertyNames, types);
if ( dirtyProperties==null )
{
interceptorHandledDirtyCheck = false;
cannotDirtyCheck = entry.LoadedState==null;
if ( !cannotDirtyCheck )
{
dirtyProperties = persister.FindDirty(values, entry.LoadedState, obj, this);
}
}
else
{
cannotDirtyCheck = false;
interceptorHandledDirtyCheck = true;
}
if ( persister.IsMutable && (cannotDirtyCheck ||
(dirtyProperties!=null && dirtyProperties.Length!=0 ) ||
(status==Status.Loaded && persister.IsVersioned &&
persister.HasCollections &&
SearchForDirtyCollections(values, types) ) ) )
{
// 对象是脏的(dirty)。
bool intercepted = interceptor.OnFlushDirty(
obj, entry.Id, values, entry.LoadedState, persister.PropertyNames, types);
if(intercepted && !cannotDirtyCheck && !interceptorHandledDirtyCheck)
{
dirtyProperties = persister.FindDirty(values, entry.LoadedState, obj, this);
}
substitute = substitute || intercepted;
if(status == Status.Loaded && persister.ImplementsValidatable)
{
((IValidatable)obj).Validate();
}
object[] updatedState = null;
if(status==Status.Loaded)
{
updatedState = new object[values.Length];
TypeFactory.DeepCopy(values, types, persister.PropertyUpdateability, updatedState);
}
updates.Add(
new ScheduledUpdate(entry.Id, values, dirtyProperties, entry.Version,
nextVersion, obj, updatedState, persister, this)
);
}
}
}
}
首先取得属性的值,这个通过对象的持久化类的GetPropertyValues方法来获得,这个方法与前面设置对象属性的SetPropertyValues是相反的;
然后查找已更改的属性,NH把控制权交给了interceptor(拦截器),在FindDirty中,我们可以设置已发生更改的属性,这看起来是个不错的处理,例如:在字段级权限中,可以根据权限限制某些属性不被更改。如果不进行处理,一定要记得返回一个null值哦,这时由对象的持久化类来查找已更改的属性(FindDirty方法),如果检查出对象是脏的(dirty), 就进行后续处理。
接下来NH再次将控制权交给了interceptor, 这次调用的是OnFlushDirty, 这样我们又得到了一个处理对象的机会,至于能做些什么,那就要看大家的想像力了,最简单的就是记录更新日志log。:-)
注意这个方法有个返回值,默认是返回false, 如果你修改了values的内容,并且需要更新,那么应返回true, 这将再次调用对象持久化类的FindDirty方法,前提是你没有自行处理interceptor的FindDirty方法。
再接下来,检查对象是否实现了IValidatable(验证)接口, 如有实现则调用IValidatable.Validate方法。强烈建议所有的持久对象都应实现IValidable, 虽然可以在映射文件中指定一些约束,但这些约束的处理显然比在IValidatable接口中处理要晚,一个比较关健的是可以提供一致的数据校验接口(如验证失败,抛出一个ValidateException, 并附上验证失败的错误信息),这样在表示层或业务层只要一个简单的处理就可以了。
最后将对象加入更新集合中,等待下一步处理。
// *** Session.cs 2392行 ***
private void Execute()
{
try
{
ExecuteAll( insertions );
insertions.Clear();
ExecuteAll( updates );
updates.Clear();
ExecuteAll( collectionRemovals );
collectionRemovals.Clear();
ExecuteAll( collectionUpdates );
collectionUpdates.Clear();
ExecuteAll( collectionCreations );
collectionCreations.Clear();
ExecuteAll( deletions );
deletions.Clear();
}
catch (Exception e)
{
throw new ADOException("could not synchronize database state with session", e);
}
}
处理所有存放在任务集合中的对象。
// *** Session.cs 2450行 ***
private void ExecuteAll(ICollection coll)
{
foreach(IExecutable e in coll)
{
executions.Add(e);
e.Execute();
}
if ( batcher!=null ) batcher.ExecuteBatch();
}
遍历coll, 然后调用IExecutable.Execute方法进行真正的持久化操作。
// *** ScheduledUpdate.cs 33行 ***
public override void Execute()
{
if ( Persister.HasCache ) Persister.Cache.Lock(Id);
Persister.Update(Id, fields, dirtyFields, lastVersion, Instance, Session);
Session.PostUpdate(Instance, updatedState, nextVersion);
}
通过对象的持久化类进行更新操作。
// *** EntityPersister.cs 998行 ***
protected virtual void Update(object id, object[] fields, bool[] includeProperty,
object oldVersion, object obj, SqlString sqlUpdateString, ISessionImplementor session)
{
if (!hasUpdateableColumns) return;
IDbCommand statement = null;
statement = session.Batcher.PrepareBatchCommand( sqlUpdateString );
try
{
int versionParamIndex = Dehydrate(id, fields, includeProperty, statement, session);
session.Batcher.AddToBatch(1);
}
catch (Exception e)
{
session.Batcher.AbortBatch(e);
throw e;
}
}
上面的代码看起来有点怪,不过显然是Batcher(批处理器)尚未完成的代码。
Dehydrate用于把与字段对应的属性值赋值到statement的参数中。
// *** Session 2438行 ***
public void PostUpdate(object obj, object[] updatedState, object nextVersion)
{
EntityEntry e = GetEntry(obj);
e.LoadedState = updatedState;
e.LockMode = LockMode.Write;
if(e.Persister.IsVersioned)
{
e.Version = nextVersion;
e.Persister.SetPropertyValue(obj, e.Persister.VersionProperty, nextVersion);
}
}
调整session实体集合中对象的状态。
// *** Session.cs 2852行 ***
private void PostFlush()
{
foreach(DictionaryEntry de in IdentityMap.ConcurrentEntries(collections))
{
((CollectionEntry) de.Value).PostFlush( (PersistentCollection) de.Key );
}
interceptor.PostFlush( entitiesByKey.Values );
}
调用拦截器的PostFlush方法,传递会话中的所有实体。
数据更新的持久化操作到此就结束了,与我们设想的一点出入,就是最后并没把最新的属性值存入到实体集合中,只是更改了LoadedState和LockMode,
至于为什么,就能给大家去思考一些吧?
持久化操作由与持久对象关联的持久化类来完成,持久化类是实现IClassPersister接口的类,每个持久对象都有一个关联的持久化类,这些持久化类存储在会话工厂的classPersisters集合中,nhibernate允许用户通过自定义的持久化类来持久化数据。
IClassPersister接口定义了基本的CRUD操作,在nhibernate中由AbstractEntityPersister类实现,这是一个抽象类,有两个具体的派生类,分别是:EntityPersister和normalizedEntityPersister,前者用于一个表一个类的情况,后面用于一个表一个子类的情况。
在分析持久化操作之前先来介绍几个辅助类:
1. SqlString: 用于构造IDbCommand对象;
2. SqlStringBuilder: 用于构造SqlString对象;
3. SqlInsertBuilder: 用于构造Insert操作的SqlString对象;
4. SqlUpdateBuilder: 用于构造Update操作的SqlString对象;
5. SqlDeleteBuilder: 用于构造Delete操作的SqlString对象;
6. Parameter: 用于转换到实现IDbParameter接口对象;
7. IPrepare: 用于准备和存储IDbCommand接口,由PrepareImpl实现。
下面以一个有identity符识的对象为例说明其持久化的流程。
一. Insert
因为持久对象有identity标识符,所以执行Save操作时,是立即调用持久对象的持久化类来执行Insert操作,而不是加入到计划集合中(原因请参考 nhibernate源码分析之三)。但最终的处理方式是一致的。
//*** EntityPersister.cs ***
public override void Insert(object[] fields, object obj, ISessionImplementor session)
{
if(UseDynamicInsert)
{
bool[] notNull = GetNotNullInsertableColumns(fields);
Insert(fields, notNull, GenerateInsertString(false, notNull), obj, session);
}
else
{
Insert(fields, PropertyInsertability, SqlInsertString, obj, session);
}
}
Insert方法首先检查是否使用dynamic-insert(动态插入),如使用则只插入非空的字段,dynamic-insert可在映射文件中指定;然后通过GenerateInsertString方法取得insert操作的SqlString。
protected virtual SqlString GenerateInsertString(bool identityInsert, bool[] includeProperty)
{
SqlInsertBuilder builder = new SqlInsertBuilder(factory);
builder.SetTableName(TableName);
for(int i = 0 ; i < hydrateSpan; i++)
{
if(includeProperty[i]) builder.AddColumn(propertyColumnNames[i], PropertyTypes[i]);
}
if (IsPolymorphic) builder.AddColumn(DiscriminatorColumnName, DiscriminatorSQLString);
if(identityInsert==false)
{
builder.AddColumn(IdentifierColumnNames, IdentifierType);
}
else
{
if(dialect.IdentityInsertString!=null)
{
builder.AddColumn(IdentifierColumnNames[0], dialect.IdentityInsertString);
}
}
return builder.ToSqlString();
}
首先构造一个SqlInsertBuilder对象,然后加入所有要插入的字段,hydrateSpan在AbstractEntityPersister类中定义,是持久对象的属性Count;然后判断持久对象是否为Polymorphic(通过字段值实现多态),如是则加入辨别列,辨别值可通过映射文件中的discriminator-value来指定;接着判断是否要加入标识列,最后返回一个SqlString对象。注意:在ToSqlString方法中,可以看到我们熟悉的INSERT INTO语句。
SqlInsertBuilder类的AddColumn方法有多个重载,这里只列出比较重要的一个(加入参数列)。
//*** SqlInsertBuilder.cs ***
public SqlInsertBuilder AddColumn(string[] columnNames, IType propertyType)
{
Parameter[] parameters = Parameter.GenerateParameters(factory, columnNames, propertyType);
for(int i = 0; i < columnNames.Length; i++)
{
this.columnNames.Add(columnNames[i]);
columnValues.Add(parameters[i]);
}
return this;
}
通过Parameter的静态方法GenerateParameters创建Parameter,然后加入到集合中。
//*** EntityPersister.cs ***
public object Insert(object[] fields, bool[] notNull, SqlString sql,
object obj, ISessionImplementor session)
{
IDbCommand statement = null;
IDbCommand idSelect = null;
if(dialect.SupportsIdentitySelectInInsert)
{
statement = session.Preparer.PrepareCommand( dialect.AddIdentitySelectToInsert(sql) );
idSelect = statement;
}
else
{
statement = session.Preparer.PrepareCommand(sql);
idSelect = session.Preparer.PrepareCommand(SqlIdentitySelect);
}
try
{
Dehydrate(null, fields, PropertyInsertability, statement, session);
}
catch (Exception e)
{
throw new HibernateException("...", e);
}
try
{
if(dialect.SupportsIdentitySelectInInsert==false)
{
statement.ExecuteNonQuery();
}
IDataReader rs = idSelect.ExecuteReader();
object id;
try
{
if ( !rs.Read() ) throw new HibernateException("...");
id = IdentifierGeneratorFactory.Get( rs, IdentifierType.ReturnedClass );
}
finally
{
rs.Close();
}
return id;
}
catch (Exception e)
{
throw e;
}
}
首先声明两个IDbCommand对象,statement用于insert,idSelect用于检查标识符,然后通过IPreparer接口来准备IDbCommand对象, 如dialect(数据库方言)支持在Insert语句中后检索标识符,则将insert与检查标识符操作合并;然后调用Dehydrate对参数进行赋值;最后执行IDbCommand并返回id。
IPreparer用于准备IDbCommand对象,并将当前会话中已执行过的IDbCommand对象存储在hashtable表中,这样对于批量操作时可以提高性能,而IDbCommand对象则由SqlString对象创建。
//*** SqlString.cs ***
public IDbCommand BuildCommand(IDriver driver)
{
int paramIndex = 0;
IDbCommand cmd = driver.CreateCommand();
StringBuilder builder = new StringBuilder(sqlParts.Length * 15);
for(int i = 0; i < sqlParts.Length; i++)
{
object part = sqlParts[i];
Parameter parameter = part as Parameter;
if(parameter!=null)
{
string paramName = "p" + paramIndex;
builder.Append( parameter.GetSqlName(driver, paramName) );
IDbDataParameter dbParam = parameter.GetIDbDataParameter(cmd, driver, paramName);
cmd.Parameters.Add(dbParam);
paramIndex++;
}
else
{
builder.Append((string)part);
}
}
cmd.CommandText = builder.ToString();
return cmd;
}
通过Driver创建IDbCommand对象,如果part(组成Sql语句的一部分)为Parameter,则创建IDbDataParameter并加入到IDbCommand中。所有的part就组成Sql语句。
//*** EncityPersister ***
protected virtual int Dehydrate(object id, object[] fields, bool[] includeProperty,
IDbCommand st, ISessionImplementor session)
{
int index = 0;
for (int j=0; j<includeProperty.length; j++)
{
if ( includeProperty[j] )
{
PropertyTypes[j].NullSafeSet( st, fields[j], index, session );
index += propertyColumnSpans[j];
}
}
if ( id!=null )
{
IdentifierType.NullSafeSet( st, id, index, session );
index += IdentifierColumnNames.Length;
}
return index;
}
Dehydrate遍历持久对象的属性,并调用属性类型(IType接口)的NullSafeSet方法对IDbCommand对象中的参数进行赋值,PropertyTypes在AbstractEntityPersister类中定义,为所有属性类型的集合。
//*** NullableType.cs ***
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value==null)
{
( (IDataParameter)cmd.Parameters[index] ).Value = DBNull.Value;
}
else
{
Set(cmd, value, index);
}
}
如为null,则设置参数值为DBNull.Value;否则调用Set方法,Set方法为virtual方法。
对象标识相当于数据表中的主键,在持久化中起着十分重要的作用,nhibernate通过对象标识来辨别两个持久对象是否相等。
在映射文件中,通过id属性来定义对象标识,内容如下:
<id name="orderId" type="Int32" unsaved-value="0" column="order_id">
<generator class=identity />
</id>
其中unsaved-value属性用来指明对象未持久化时的值,如果此值与未持久化的对象标识值不符,将无法save对象,generator用于指定标识对象的类型,常用的有identity, assigned等。
标识对象为实现IIdentitierGenerator接口的类,由IdentitierGeneratorFactory类根据映射文件的标识类型来创建,IIdentifierGenerator定义了Generate方法,用于产生对象标识。
1. 标识对象的建立
标识对象在持久化类AbstractEntityPersister中创建,通过它我们就可以对持久对象的标识进行操作了。
//*** AbstractEntityPersister.cs ***
public virtual IIdentifierGenerator IdentifierGenerator
{
get
{
if (idgen==null)
{
throw new HibernateException("...");
}
return idgen;
}
}
idgen在构造函数中被赋值。
protected AbstractEntityPersister(PersistentClass model, ISessionFactoryImplementor factory)
{
// ...
// GENERATOR
idgen = model.Identifier.CreateIdentifierGenerator(dialect);
useIdentityColumn = idgen is IdentityGenerator;
identitySelectString = useIdentityColumn ? dialect.IdentitySelectString : null;
// ...
}
其中model为PersistentClass或其子类,Identifier为Value类型的属性。
// *** Value.cs ***
public IIdentifierGenerator CreateIdentifierGenerator(Dialect.Dialect dialect)
{
if ( uniqueIdentifierGenerator==null )
{
uniqueIdentifierGenerator = IdentifierGeneratorFactory.Create(identifierGeneratorStrategy, type, identifierGeneratorProperties, dialect);
}
return uniqueIdentifierGenerator;
}
//*** IdentitifierGeneratorFactory ***
public static IIdentifierGenerator Create(string strategy, IType type, IDictionary parms, Dialect.Dialect dialect)
{
try
{
System.Type clazz = (System.Type) idgenerators[strategy];
// ...
if (clazz==null) clazz = System.Type.GetType(strategy);
IIdentifierGenerator idgen = (IIdentifierGenerator) Activator.CreateInstance(clazz);
if (idgen is IConfigurable) ((IConfigurable) idgen).Configure(type, parms, dialect);
return idgen;
}
catch (Exception e)
{
throw new MappingException("could not instantiate id generator", e);
}
}
Create方法通过标识对象类名来创建标识对象。
2. 标识对象在持久化中的使用
在会话和持久化操作一文,我曾提到当前会话会把要持久化的对象存储起来,直到调用Flush或关闭会话。存储持久对象的集合为entitiesByKey,这是一个Hashtable,它的key为一个Key对象, value为持久对象,Key对象简单的存储持久对象的id和IdentifierSpace。
在进行持久化操作时,nhibernate必须首先检查对象是否在entitiesByKey中,这由GetEntity方法完成,然后再根据对象是否在集合中作后续处理。
//*** SessionImpl.cs ***
public object GetEntity(Key key)
{
return entitiesByKey[key];
}
下面来看看DoUpdate中的处理:
private void DoUpdate(object obj, object id)
{
// ...
Key key = new Key(id, persister);
object old = GetEntity(key);
if (old==obj)
{
throw new AssertionFailure("Hibernate has a bug in Update() ... or you are using an illegal id type");
}
else if ( old!=null )
{
throw new HibernateException("Another object was associated with this id ( the object with the given id was already loaded)");
}
// ...
AddEntity(key, obj);
AddEntry(obj, Status.Loaded, null, id, persister.GetVersion(obj), LockMode.None, true, persister);
// ...
}
如果首次对持久对象执行Update,此时old为空,操作顺利执行,并且对象被加入到集合中,
当再次调用Update时(在同一会话中,并且没有调用会导致Flush的操作),此时old不为空,将引发一个异常。
NHibernate源码分析之六: Criteria数据加载
ICriteria是使用Expression进行数据加载的接口, 提供了设置表达式(Expression), 排序方式(Order), 分页记录等操作.
它使用一种类似于SQL语句where表达表的方式来加载满足条件的数据.
下面以一个查询Username为billy, Password为test的用户为例来分析nh中Criteria数据加载是怎样工作的.
查询代码如下:
Expression ex = Expression.And( Expression.Eq("Username", "billy"),
Expression.Eq("Password", "test") );
IList users = session.CreateCriteria( typeof(Username) ).Add( ex ).List();
这里先建立了一个Expression, 然后加入到ICriteria中. ICriteria通过session.CreateCriteria( type )获得.
注意: ICriteria的实现类CriteriaImpl是一个Internal类, 因此不能在nh程序集外直接创建.
SqlString和SqlStringBuilder对象
因后面的代码多次用到SqlString和SqlStringBuilder对象,这里先说明一下。
SqlString对象用于存储伪Sql语句中的各个部分,注意,是伪Sql语句哦~, 不是实际的sql语句,不过实际的sql语句是通过SqlString对象解析而来的。
假如我有这样的一个伪sql语句,
select * from User u
where u.Username = @Username and u.Password = @Password
那么由此产生的SqlString将包含一个这样的数组,{"select", "*", "from", "User", "where", "Username","=", param, "and", "Password", "=", param2 }, 其中param, param2是nh中的Paramenter(参数)对象.
SqlStringBuilder对象专门用于创建SqlString对象,因为SqlString是不能修改它内部的sqlPart的,如要修改sqlPart, 必须使用SqlStringBuilder.
Expression对象
Expression是所有表达式对象的祖先, 它使用Factory Method(工厂模式)来创建具体的表达式(如相等表达式EqExpression).
这里介绍几个最要的表达式对象:
SimpleExpression: 简单表达式对象, 是所有简单操作表达式的父类, 如EqExpression, GtExpression等;
LogicalExpression: 逻辑表达式对象, 用于指定两个表达式的逻辑关系, 有AndExpression和OrExpression两个子类;
最值的关注就是Expression如何产生它的sqlString了, 这个由ToSqlString方法完成.
// *** SimpleExpression.cs 60行***
public override SqlString ToSqlString(ISessionFactoryImplementor factory,
System.Type persistentClass, string alias)
{
SqlStringBuilder sqlBuilder = new SqlStringBuilder();
IType propertyType = ((IQueryable)factory.GetPersister(persistentClass)).GetPropertyType(propertyName);
string[] columnNames = GetColumns(factory, persistentClass, propertyName, alias);
string[] paramColumnNames = GetColumns(factory, persistentClass, propertyName , null);
Parameter[] parameters = Parameter.GenerateParameters(factory, alias, paramColumnNames, propertyType);
for(int i = 0; i < columnNames.Length; i++)
{
if(i > 0) sqlBuilder.Add(" AND ");
sqlBuilder.Add(columnNames[i])
.Add(Op)
.Add(parameters[i]);
}
return sqlBuilder.ToSqlString();
}
取得属性对应的数据列名, 并创建Parameter对象, 在解析SqlString时将根据Parameter产生实际的IDbParameter对象.
// *** SimpleExpression.cs 65行***
public override SqlString ToSqlString(ISessionFactoryImplementor factory,
System.Type persistentClass, string alias)
{
SqlStringBuilder sqlBuilder = new SqlStringBuilder();
SqlString lhSqlString = lhs.ToSqlString(factory, persistentClass, alias);
SqlString rhSqlString = rhs.ToSqlString(factory, persistentClass, alias);
sqlBuilder.Add(new SqlString[] {lhSqlString, rhSqlString},"(", Op, ")");
return sqlBuilder.ToSqlString();
}
先得到逻辑操作两边的表达式SqlString, 然后连同逻辑操作一起加入到sqlBuilder对象中。
另外一个要关注的就是GetTypedValues方法了,它返回表达式中属性的类型和值,用于给参数赋值。
加载过程源码分析
//*** SessionImpl.cs 3743行 ***
public ICriteria CreateCriteria(System.Type persistentClass)
{
return new CriteriaImpl(persistentClass, this);
}
创建一个CriteriaImpl, 并将对象类型和会话传递给去.
//*** CriteriaImpl.cs 43行 ***
public ICriteria Add(NExpression.Expression expression)
{
expressions.Add(expression);
conjunction.Add(expression);
return this;
}
将表达式加入到集合中.
//*** CriteriaImpl.cs 67行 ***
public IList List()
{
return session.Find(this);
}
调用session.Find, 看来CriteriaImpl没干实事,只是存储一些查询信息.
//*** CriteriaImpl.cs 3748行 ***
public IList Find(CriteriaImpl criteria)
{
System.Type persistentClass = criteria.PersistentClass;
ILoadable persister = (ILoadable) GetPersister(persistentClass);
CriteriaLoader loader = new CriteriaLoader(persister, factory, criteria);
// 省略若干行...
try
{
return loader.List(this);
}
catch (Exception e)
{
throw new ADOException("problem in find", e);
}
// 省略若干行...
}
先取得要查询对象的持久化类, 然后创建一个CriteriaLoader, 最后返回CriteriaLoader.List的结果.
CriteriaLoader是数据加载类Loader族中的一员, 用于实现Criteria数据加载.
//*** CriteriaLoader.cs 28行 ***
public CriteriaLoader(ILoadable persister, ISessionFactoryImplementor factory,
ICriteria criteria) : base(persister, factory)
{
this.criteria = criteria;
// 此句无用, 不知为何保留?
IEnumerator iter = criteria.IterateExpressions();
StringBuilder orderByBuilder = new StringBuilder(60);
bool commaNeeded = false;
iter = criteria.IterateOrderings();
while ( iter.MoveNext() )
{
Order ord = (Order) iter.Current;
if(commaNeeded) orderByBuilder.Append(StringHelper.CommaSpace);
commaNeeded = true;
orderByBuilder.Append(ord.ToStringForSql(factory, criteria.PersistentClass, alias));
}
RenderStatement(criteria.Expression.ToSqlString(factory, criteria.PersistentClass, alias),
orderByBuilder.ToString(), factory);
PostInstantiate();
}
首先枚举排序对象, 并组合成orderby子句(ToStringForSql比较简单, 请参考Order对象);
然后调用父类AbstractEntityLoad的RenderStatement方法生成SqlString对象;
最后调用父类Loader的PostInstantiate方法.
//*** AbstractEntityLoader.cs 72行 ***
protected void RenderStatement(SqlString condition, string orderBy,
ISessionFactoryImplementor factory)
{
SqlSelectBuilder sqlBuilder = new SqlSelectBuilder(factory);
sqlBuilder.SetFromClause(
persister.FromTableFragment(alias).Append(
persister.FromJoinFragment(alias, true, true)
)
);
sqlBuilder.AddWhereClause(condition);
sqlBuilder.SetOrderByClause(orderBy);
this.sqlString = sqlBuilder.ToSqlString();
int joins=0; // I added.
classPersisters = new ILoadable[joins+1];
classPersisters[joins] = persister;
}
创建一个SqlSelectBuilder对象, 这是一个用于建立select语句的对象, 然后加入form子句、where子句和排序字符串。
注:代码中省略了OuterJoin的处理代码, 关于OuterJoin数据加载, 将在后续文章中单独分析.
//*** SqlSelectBuilder.cs 167行 ***
public SqlString ToSqlString()
{
SqlStringBuilder sqlBuilder = new SqlStringBuilder();
sqlBuilder.Add("SELECT ")
.Add(selectClause)
.Add(" FROM ")
.Add(fromClause)
.Add(outerJoinsAfterFrom);
sqlBuilder.Add(" WHERE ");
if(whereSqlStrings.Count > 1)
{
sqlBuilder.Add((SqlString[])((ArrayList)whereSqlStrings).ToArray(typeof(SqlString)),
null, "AND", null, false);
}
else
{
sqlBuilder.Add((SqlString)whereSqlStrings[0], null, null, null, false);
}
sqlBuilder.Add(outerJoinsAfterWhere);
if (orderByClause != null && orderByClause.Trim().Length > 0)
{
sqlBuilder.Add(" ORDER BY ")
.Add(orderByClause);
}
return sqlBuilder.ToSqlString();
}
创建一个SqlStringBuilder对象并将SQL关键字和给定的查询信息组装成一个SqlString对象。
//*** CriteriaLoader.cs 55行 ***
public IList List(ISessionImplementor session)
{
ArrayList values = new ArrayList();
ArrayList types = new ArrayList();
IEnumerator iter = criteria.IterateExpressions();
while ( iter.MoveNext() )
{
Expression.Expression expr = (Expression.Expression) iter.Current;
TypedValue[] tv = expr.GetTypedValues( session.Factory, criteria.PersistentClass );
for ( int i=0; i<tv.Length; i++ )
{
values.Add( tv[i].Value );
types.Add( tv[i].Type );
}
}
object[] valueArray = values.ToArray();
IType[] typeArray = (IType[]) types.ToArray(typeof(IType));
return Find(session, valueArray, typeArray, true, criteria.Selection, null, null);
}
先枚举表达式对象,取得所有表达式中参数的值和类型,然后调用Loader.Find方法。
Loader对象的Find方法是所有数据加载的最终方法,将在后续的文章中单独分析。
NH中,HQL是一个十分强大的面向对象的查询语言,简单的说,就是不需要使用实际的表名和列名来查询数据,而改用类名和属性。
有两种方式来执行HQL数据加载,一种是直接使用ISession的Find方法,另一种是使用IQuery接口。
IQuery接口提供了一些额外的设置,最重要的就是分页了,这个和ICriteria差不多,另外一些就是设置参数的值了。
IQuery最终还是会调用ISession的Find方法,下面来分析一下IQuery中HQL语句的处理.
示例代码:
IQuery query = session.CreateQuery( " from User u where u.Name = :Name " )
query.SetString( "Name", "billy" );
IList users = query.List();
源码分析:
//*** SessionImpl.cs 1760行 ***/
public IQuery CreateQuery(string queryString)
{
return new QueryImpl(queryString, this);
}
创建一个QueryImpl对象,并传递HQL查询文本和会话.
注意: IQuery的实现类QueryImpl是一个Internal类, 因此不能在nh程序集外直接创建.
//*** QueryImpl.cs 70行 **/
public IQuery SetParameter(int position, object val, IType type)
{
int size = values.Count;
if ( position<size )
{
values[position] = val;
types[position] = type;
}
else
{
for (int i=0; i<position-size; i++)
{
values.Add(null);
types.Add(null);
}
values.Add(val);
types.Add(type);
}
return this;
}
public IQuery SetParameter(string name, object val, IType type)
{
namedParameters[name] = new TypedValue(type, val);
return this;
}
在IQuery中,参数有两种情况,一种是命名参数,一种是未命名参数, 上面给出的示例代码是使用命名参数。
SetString方法直接调用SetParameter方法来设置命名参数,SetParameter参数有多个重载的版本,用于适应不同的IType, 如果要设置未命名参数,那么应通过参数的位置来设置。
TypedValue用对象来于保存参数的类型和值。
//*** QueryImpl.cs 47行 ***/
public virtual IList List()
{
IDictionary namedParams = new Hashtable( namedParameters );
string query = BindParameterLists(namedParams);
return session.Find(query, (object[]) values.ToArray(typeof(object)),
(IType[])types.ToArray(typeof(IType)),
selection, namedParams, lockModes);
}
在执行查找之前,必须先处理所有的集合参数,像in关健字就用到了集合参数。
//*** QueryImpl.cs 280行 ***
protected string BindParameterLists(IDictionary namedParams)
{
string query = queryString;
foreach( DictionaryEntry de in namedParametersLists )
{
query = BindParameterList( query, (string) de.Key, (TypedValue) de.Value, namedParams );
}
return query;
}
所有集合参数都存储在namedParametersLists集合中(可参见SetParameterList方法),这里通过枚举对各个集合参数进行绑定。
//*** QueryImpl.cs 288行 ***
private string BindParameterList(string queryString, string name,
TypedValue typedList, IDictionary namedParams)
{
ICollection vals = (ICollection) typedList.Value;
IType type = typedList.Type;
StringBuilder list = new StringBuilder(16);
int i=0;
foreach( object obj in vals )
{
string alias = name + i++ + StringHelper.Underscore;
namedParams.Add( alias, new TypedValue( type, obj ) );
list.Append( ‘:‘ + alias );
if ( i < vals.Count ) list.Append( StringHelper.CommaSpace );
}
return StringHelper.Replace( queryString, ‘:‘ + name, list.ToString() );
}
BindParameterList方法遍历值集合,然后将它们加入到命名参数集合(namedParams)中,最后修改HQL查询文本。
例如:在HQL中是" ... name in ( :Name )",如果值集合有三个值,那么修改后的HQL是"... name in ( :Name0_, :Name1_, :Name2_ )"
//*** SessionImpl.cs 1571行 ***
public IList Find(string query, object[] values, IType[] types,
RowSelection selection,IDictionary namedParams, IDictionary lockModes)
{
QueryTranslator[] q = GetQueries(query, false);
IList results = new ArrayList();
dontFlushFromFind++; //stops flush being called multiple times if this method is recursively called
//execute the queries and return all result lists as a single list
try
{
for (int i=0; i<q.Length; i++ )
{
IList currentResults;
try
{
currentResults = q[i].FindList(this, values, types, true, selection, namedParams, lockModes);
}
catch (Exception e)
{
throw new ADOException("Could not execute query", e);
}
for (int j=0;j<results.Count;j++)
{
currentResults.Add( results[j] );
}
results = currentResults;
}
}
finally
{
dontFlushFromFind--;
}
return results;
}
根据HQL查询文本生成QueryTranslator对象,然后调用QueryTranslator的FindList方法返回查询结果。
//*** QueryTranslator.cs 1051行 ***
public IList FindList( ISessionImplementor session, object[] values, IType[] types,
bool returnProxies, RowSelection selection, IDictionary namedParams, IDictionary lockModes)
{
return base.Find(session, values, types, returnProxies, selection, namedParams, lockModes);
}
FindList直接调用父类Loader的Find方法来查询数据,所有的数据加载(当然包括Criteria加载)最终都由Loader进行处理,关于这个类,将在后续的文章中单独进行分析。
//*** SessionImpl.cs 1616行***
private QueryTranslator[] GetQueries(string query, bool scalar)
{
// a query that naemes an interface or unmapped class in the from clause
// is actually executed as multiple queries
string[] concreteQueries = QueryTranslator.ConcreteQueries(query, factory);
// take the union of the query spaces (ie the queried tables)
QueryTranslator[] q = new QueryTranslator[concreteQueries.Length];
ArrayList qs = new ArrayList();
for (int i=0; i<concreteQueries.Length; i++ )
{
q[i] = scalar ? factory.GetShallowQuery( concreteQueries[i] )
: factory.GetQuery( concreteQueries[i] );
qs.AddRange( q[i].QuerySpaces );
}
return q;
}
分析HQL查询文本,如果是由多个SQL语句组成,就要将其进行分割,最后由会话工厂根据HQL文本创建QueryTranslator对象。
//*** SessionFactoryImpl.cs 429行 ***
private QueryTranslator GetQuery(string query, bool shallow)
{
QueryCacheKey cacheKey = new QueryCacheKey(query, shallow);
QueryTranslator q = (QueryTranslator) Get(cacheKey);
if ( q==null)
{
q = new QueryTranslator(dialect);
Put(cacheKey, q);
}
q.Compile(this, query, querySubstitutions, shallow);
return q;
}
检查在缓存中是否已存在相同的QueryTranslator对象,如没有,则创建一个并将其放入缓存,
然后调用QueryTranslator的Compile方法对HQL查询文本进行解析, 有关解析SQL的细节,请参考解析HQL语句。
本文将对HQL查询文本的解析过程进行分析,这个可以说是NH中比较复杂的一块了(个人认为),涉及到的类也比较多。
建议阅读之前先深呼吸十下,看完之后脑袋成浆糊可不要找我哟。:-)
在HQL数据加载一文中,我们有提到QueryTranslator的创建过程,代码如下:
//*** SessionFactoryImpl.cs 429行 ***
private QueryTranslator GetQuery(string query, bool shallow)
{
QueryCacheKey cacheKey = new QueryCacheKey(query, shallow);
QueryTranslator q = (QueryTranslator) Get(cacheKey);
if ( q==null)
{
q = new QueryTranslator(dialect);
Put(cacheKey, q);
}
q.Compile(this, query, querySubstitutions, shallow);
return q;
}
当创建(或从缓存取得)QueryTranslator对象后,就调用它的Compile方法对HQL文本进行解析,
参数querySubstitutions是用于指定要替换的关健字,它可在配置文件中给出。
//*** QueryTranslator.cs 116行 ***
[MethodImpl(MethodImplOptions.Synchronized)] //方法的同步属性
public void Compile(ISessionFactoryImplementor factory, string queryString,
IDictionary replacements, bool scalar)
{
if (!compiled)
{
this.factory = factory;
this.replacements = replacements;
this.shallowQuery = scalar;
Compile(queryString);
}
}
这个方法上加上了Synchronized选项,指定这是一个单线程的方法(同一时间只能有一个线程执行此方法,与使用Lock是等价的),不知出于什么原因考虑?
//*** QueryTranslator.cs 132行 ***
protected void Compile(string queryString)
{
this.queryString = queryString;
try
{
ParserHelper.Parse(
new PreprocessingParser(replacements),
queryString,
ParserHelper.HqlSeparators,
this);
RenderSql();
}
catch (QueryException qe)
{
qe.QueryString = queryString;
throw qe;
}
catch (MappingException me)
{
throw me;
}
catch (Exception e)
{
log.Debug("unexpected query compilation problem", e);
QueryException qe = new QueryException("Incorrect query syntax", e);
qe.QueryString = queryString;
throw qe;
}
PostInstantiate();
compiled = true;
}
将解析HQL文本的任务交给ParserHelper处理,这是一个用于解析的帮助类,注意Parser方法的第一个参数是一个PreprocessingParser对象。
//*** ParserHelper.cs 25行 ***
public static void Parse(IParser p, string text, string seperators, QueryTranslator q)
{
StringTokenizer tokens = new StringTokenizer(text, seperators, true);
p.Start(q);
foreach(string token in tokens)
{
p.Token(token, q);
}
p.End(q);
}
首先创建一个StringTokenizer对象,这是一个迭代HQL文本的类,稍后有详细分析。然后启动解析器,并迭代处理HQL文本片断。
所有的处理HQL文本片断的解析器都实现了IParser接口。
public interface IParser
{
void Token(string token, QueryTranslator q);
void Start(QueryTranslator q);
void End(QueryTranslator q);
}
StringTokenizer类
StringTokenizer类是一个实现了枚举接口IEnumerable的类, 用于迭代HQL文本中的片断,下面详细说明一下。
public class StringTokenizer : IEnumerable
{
//*** 28行 ***
public StringTokenizer(string str, string delim, bool returnDelims)
{
_origin = str; // 要处理的HQL查询文本。
_delim = delim; // 分隔符, 迭代器以分隔符为准返回片断。
// HQL分隔符在ParseHelper中定义,为" /n/r/f/t,()=<>&|+-=/*‘^![]#~//"
_returnDelim = returnDelims; // 指定是否返回分隔符。
}
public IEnumerator GetEnumerator()
{
return new StringTokenizerEnumerator(this);
}
枚举器,可用于foreach操作。
private class StringTokenizerEnumerator : IEnumerator
{
private StringTokenizer _stokenizer;
private int _cursor = 0;
private String _next = null;
// 此处省略部分代码...
private string GetNext()
{
char c;
bool isDelim;
// 检查当前光标是否已超出HQL文本的长度.
if( _cursor >= _stokenizer._origin.Length )
return null;
c = _stokenizer._origin[_cursor];
isDelim = (_stokenizer._delim.IndexOf(c) != -1);
// 检查当前字符是否为HQL分隔符.
if ( isDelim )
{
_cursor++;
if ( _stokenizer._returnDelim )
{
return c.ToString(); // 如允许返回分隔符,则返回它。
}
return GetNext(); // 处理下一片断。
}
// 取得下一分隔符的位置。
int nextDelimPos = _stokenizer._origin.IndexOfAny(_stokenizer._delim.ToCharArray(), _cursor);
if (nextDelimPos == -1)
{
nextDelimPos = _stokenizer._origin.Length;
}
// 取得当前光标至下一分隔符之前的字符串片断并返回。
string nextToken = _stokenizer._origin.Substring(_cursor, nextDelimPos - _cursor);
_cursor = nextDelimPos;
return nextToken;
}
}
}
现在就不难理解ParserHelper.Parse方法中的这个代码了,
foreach(string token in tokens)
{
p.Token(token, q);
}
这里的token就是我们从枚举器中取得的字符串片断了。
//*** PreprocessingParser.cs 58行 ***
public void Token(string token, QueryTranslator q)
{
//handle quoted strings
if (quoted)
{
quotedString.Append(token);
}
if ("‘".Equals(token))
{
if (quoted)
{
token = quotedString.ToString();
}
else
{
quotedString = new StringBuilder(20).Append(token);
}
quoted = !quoted;
}
if (quoted) return;
//ignore whitespace
if (ParserHelper.IsWhitespace(token)) return;
//do replacements
string substoken = (string) replacements[token];
token = (substoken == null) ? token : substoken;
//handle HQL2 collection syntax
if (currentCollectionProp != null)
{
if (StringHelper.OpenParen.Equals(token))
{
return;
}
else if (StringHelper.ClosedParen.Equals(token))
{
currentCollectionProp = null;
return ;
}
else
{
token += StringHelper.Dot + currentCollectionProp;
}
}
else
{
string prop = (string) collectionProps[token.ToLower()];
if (prop != null)
{
currentCollectionProp = prop;
return ;
}
}
//handle <=, >=, !=, is not, not between, not in
if (lastToken == null)
{
lastToken = token;
}
else
{
string doubleToken = (token.Length > 1)?
lastToken + ‘ ‘ + token :
lastToken + token;
if (operators.Contains(doubleToken.ToLower()))
{
parser.Token(doubleToken, q);
lastToken = null;
}
else
{
parser.Token(lastToken, q);
lastToken = token;
}
}
}
PreprocessingParser解析器主要处理引号、空格、替换、集合以及一些操作符,parser是ClauseParser的实例,用于后续的解析处理。
//*** ClauseParser.cs 19行 ***
public virtual void Token(string token, QueryTranslator q)
{
string lcToken = token.ToLower();
if ( token.Equals(StringHelper.OpenParen ) )
{
parenCount++;
}
else if ( token.Equals(StringHelper.ClosedParen ) )
{
parenCount--;
}
if (byExpected && !lcToken.Equals("by"))
{
throw new QueryException("BY expected after GROUP or ORDER: " + token);
}
bool isClauseStart = parenCount==0; //ignore subselect keywords
if (isClauseStart)
{
if (lcToken.Equals("select"))
{
selectTokens = new ArrayList();
cacheSelectTokens = true;
}
else if (lcToken.Equals("from"))
{
child = new FromParser();
child.Start(q);
cacheSelectTokens = false;
}
else if (lcToken.Equals("where"))
{
EndChild(q);
child = new WhereParser(q.dialect);
child.Start(q);
}
else if (lcToken.Equals("order"))
{
EndChild(q);
child = new OrderByParser();
byExpected = true;
}
else if (lcToken.Equals("having"))
{
EndChild(q);
child = new HavingParser(q.dialect);
child.Start(q);
}
else if (lcToken.Equals("group"))
{
EndChild(q);
child = new GroupByParser();
byExpected = true;
}
else if (lcToken.Equals("by"))
{
if (!byExpected) throw new QueryException("GROUP or ORDER expected before BY");
child.Start(q);
byExpected = false;
}
else
{
isClauseStart = false;
}
}
if (!isClauseStart)
{
if (cacheSelectTokens)
{
selectTokens.Add(token);
}
else
{
if (child == null)
{
throw new QueryException("query must begin with SELECT or FROM: " + token);
}
else
{
child.Token(token, q);
}
}
}
}
在这里,根据不同token, 创建不同的子解析器进行解析。
//*** QueryTranslator.cs 522行 ***
private void RenderSql()
{
int rtsize;
if (returnTypes.Count == 0 && scalarTypes.Count == 0)
{
//ie no select clause in HQL
returnTypes = fromTypes;
rtsize = returnTypes.Count;
}
else
{
rtsize = returnTypes.Count;
foreach(string entityName in entitiesToFetch)
{
returnTypes.Add(entityName);
}
}
int size = returnTypes.Count;
names = new string[size];
persisters = new IQueryable[size];
suffixes = new string[size];
includeInSelect = new bool[size];
for (int i=0; i<size; i++)
{
string name = (string) returnTypes[i];
persisters[i] = GetPersisterForName(name);
suffixes[i] = (size==1) ? String.Empty :
i.ToString() + StringHelper.Underscore;
names[i] = name;
includeInSelect[i] = !entitiesToFetch.Contains(name);
if ( includeInSelect[i] ) selectLength++;
if ( name.Equals(collectionOwnerName) ) collectionOwnerColumn = i;
}
string scalarSelect = RenderScalarSelect();
int scalarSize = scalarTypes.Count;
hasScalars = scalarTypes.Count!=rtsize;
types = new IType[scalarSize];
for (int i=0; i<scalarSize; i++ )
{
types[i] = (IType) scalarTypes[i];
}
QuerySelect sql = new QuerySelect( factory.Dialect );
sql.Distinct = distinct;
if ( !shallowQuery )
{
RenderIdentifierSelect(sql);
RenderPropertiesSelect(sql);
}
if ( CollectionPersister!=null )
{
sql.AddSelectFragmentString( collectionPersister.MultiselectClauseFragment(fetchName) );
}
if ( hasScalars || shallowQuery ) sql.AddSelectFragmentString(scalarSelect);
MergeJoins( sql.JoinFragment );
sql.SetWhereTokens(whereTokens);
sql.SetGroupByTokens(groupByTokens);
sql.SetHavingTokens(havingTokens);
sql.SetOrderByTokens(orderByTokens);
if ( CollectionPersister!=null && CollectionPersister.HasOrdering )
{
sql.AddOrderBy( CollectionPersister.GetSQLOrderByString(fetchName) );
}
scalarColumnNames = GenerateColumnNames(types, factory);
// initialize the set of queries identifer spaces
foreach(string name in collections.Values)
{
CollectionPersister p = GetCollectionPersister(name);
AddIdentifierSpace( p.QualifiedTableName );
}
foreach(string name in typeMap.Keys)
{
IQueryable p = GetPersisterForName( name );
AddIdentifierSpace( p.IdentifierSpace );
}
sqlString = sql.ToQuerySqlString();
System.Type[] classes = new System.Type[types.Length];
for (int i=0; i<types.Length; i++)
{
if (types[i]!=null) classes[i] = (types[i] is PrimitiveType) ?
((PrimitiveType) types[i]).PrimitiveClass :
types[i].ReturnedClass;
}
try
{
if (holderClass!=null) holderConstructor = holderClass.GetConstructor(classes);
}
catch(Exception nsme)
{
throw new QueryException("could not find constructor for: " + holderClass.Name, nsme);
}
}
产生一个SqlString对象,并取得要返回的实体和字段。
解析器
下面用一些实际的HQL查询文本来分析解析器的处理过程。
一. " from User "
这应该算是一个最简单的HQL了, 以下内容所述的分发均指从ProcessingParser.Token中分派到ClauseParser的字符串片断。
第一次分发,得到一个"form"(空格被忽略),ClauseParser.Token中以下代码被执行:
else if (lcToken.Equals("from"))
{
child = new FromParser();
child.Start(q);
cacheSelectTokens = false;
}
创建一个FormParser子解析器并启动。
注意:因为isClauseStart为true, 所以,child.Token并不会被调用。
第二次分发,得到一个"User", ClauseParser.Token中以下代码被执行:
else
{
isClauseStart = false;
}
设置isClauseStart为false, 所以,child.Token被调用。
//*** FormParser.cs 38行 ***
public void Token(string token, QueryTranslator q)
{
// start by looking for HQL keywords....
string lcToken = token.ToLower();
if ( lcToken.Equals(StringHelper.Comma) )
{
}
else if ( lcToken.Equals("join") )
{
}
else if ( lcToken.Equals("fetch") )
{
}
else if ( lcToken.Equals("outer") )
{
}
else if ( joinTypes.Contains(lcToken) )
{
}
else if (lcToken.Equals("class"))
{
}
else if ( lcToken.Equals("in") )
{
}
else if ( lcToken.Equals("as") )
{
}
else
{
if ( afterAs || expectingAs )
{
}
else if (afterIn)
{
}
else
{
IQueryable p = q.GetPersisterUsingImports(token);
if (p!=null)
{
// starts with the name of a mapped class (new style)
if (joinType!=JoinType.None) throw new QueryException("...");
entityName = q.CreateNameFor( p.MappedClass );
q.AddFromClass( entityName, p );
expectingAs = true;
}
else if ( token.IndexOf(‘.‘) < 0 )
{
}
else
{
}
}
}
}
上面的代码只给出了我们的例子会执行的代码,其余的部分都省略了,可参见源码。
首先通过token取类持久化对象,在本例中,得到的是User对象的类持久化对象,然后根据映射的类建立一个实体名称,最后将实体名称加入到QueryTranslator的formTypes集合中。
对数据库的操作是少不了事务处理的,事务能保整数据完整性和有效性。在nh中,使用Transaction对象对.net的事务对象(实现了IDbTransaction接口的对象)进行了包装。
在nh中,一个典型的事务处理是这样的(见ISession.cs的注释)
ISession sess = factory.OpenSession();
Transaction tx;
try
{
tx = sess.BeginTransaction();
//do some work
...
tx.Commit();
}
catch (Exception e)
{
if (tx != null) tx.Rollback();
throw e;
}
finally
{
sess.Close();
}
事务对象由session的BeginTransaction取得,同时事务开始,如果执行顺利则提交事务,否则回滚事务。
先来看看事务对象的创建
public ITransaction BeginTransaction()
{
callAfterTransactionCompletionFromDisconnect = false;
transaction = factory.TransactionFactory.BeginTransaction(this);
return transaction;
}
很简单,直接调用事务工厂并开始事务。
public ITransaction BeginTransaction(ISessionImplementor session)
{
Transaction tx = new Transaction(session);
tx.Begin();
return tx;
}
构造一个事务对象,并开始事务。
//*** Transaction.cs 34行 ***
public void Begin()
{
try
{
IsolationLevel isolation = session.Factory.Isolation;
if( isolation==IsolationLevel.Unspecified )
{
trans = session.Connection.BeginTransaction();
}
else
{
trans = session.Connection.BeginTransaction( isolation );
}
}
catch( Exception e )
{
throw new TransactionException("Begin failed with SQL exception", e);
}
begun = true;
}
根据隔离级别启动一个事务,注意是从数据连接开始一个事务哦。
从上面可以看出nh对数据库事务的包装是十分简单的。
在这种典型的事务处理方法中,transaction依赖于一个特定的session对象, 因为session关闭时将断开数据连接,如果没有提交事务,那么事务是提交还是回滚是由具体的数据库决定的, 但无论怎样,事务都会结束。
这样的话就存在一个问题,在面向对象开发的情况下,可能有一个事务操作的多个步骤分散在不同的对象中,为了保证此事务操作的原子性,显然需要将session传递到各个步骤(方法)中,这样才能保证所有步骤都在一个session(事务)中处理。
在nh中,数据库事务的生命周期是可以长于session的生命周期的,就是说数据库事务不必依赖于一个特定的session对象,这看起来不错,我们不用再传递session对象了。注意,我这里指的是数据库事务(IDbTransaction)。
下面就来实现这种事务处理方法,代码如下:
ISessionFactoryImplementor sfe = factory as ISessionFactoryImplementor;
IDbConnection conn = sfe.OpenConnection();
IDbTransaction trans;
try
{
trans = conn.BeginTransaction(); // 开始事务,这里也可以设置事务隔离级别。
ISession s = factory.OpenSession(conn);
// do something
// ...
s.Close();
ISession s2 = factory.OpenSession(conn);
// do something
// ...
s2.Close();
trans.Commit(); // 提交事务
}
catch (Exceptin ex)
{
if ( trans != null ) trans.Rollback(); // 回滚事务
throw ex;
}
finally
{
conn.CloseConnection(conn);
}
首先取得数据库连接,然后开始事务,接着创建会话处理业务操作,注意在创建会话的时候把数据加连接也传递了给去,这是必需的。上面代码中最值的注意的就是会话的Close操作了,这个操作在默认情况下是会关闭数据库连接的,在这里之所以没有关闭连接,是因为OpenSession操作有带一个连接的缘故。
下面来看看nh中的相关代码:
public ISession OpenSession(IDbConnection connection)
{
return OpenSession(connection, interceptor);
}
public ISession OpenSession(IDbConnection connection, IInterceptor interceptor)
{
return OpenSession( connection, false, long.MinValue, interceptor );
}
private ISession OpenSession(IDbConnection connection, bool autoClose,
long timestamp, IInterceptor interceptor)
{
return new SessionImpl( connection, this, autoClose, timestamp, interceptor );
}
这里有一个关健的参数autoClose, 它指session是否自动关闭数据库连接。
public IDbConnection Close()
{
try
{
return (connection==null) ? null : Disconnect();
}
finally
{
Cleanup();
}
}
public IDbConnection Disconnect()
{
try
{
if (connect)
{
connect = false;
return null;
}
else
{
if (connection==null) throw new HibernateException("session already disconnected");
if (batcher!=null) batcher.CloseCommands();
IDbConnection c = connection;
connection=null;
if (autoClose)
{
factory.CloseConnection(c);
return null;
}
else
{
return c;
}
}
}
finally
{
if ( callAfterTransactionCompletionFromDisconnect )
{
AfterTransactionCompletion();
}
}
}
从上面的代码中可以看出,如果autoClose为true才会自动关闭连接。
这样就实现了数据库事务的生命周期长于会话生命周期的事务处理方法,至于这种方法有没使用性,那就不好说了,因为要在OpenSession时使用数据库连接,那么如果我们把操作分散于各个对象之中的话,显然必须传递数据库连接或事务,但至少做到了数据库事务与会话无关。