NHibernate源码三(续):数据持久化
下面对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,
至于为什么,就能给大家去思考一些吧?