NHibernate源码分析之三(续):数据持久化

当持久化对象时,显然必须存在把记录的值赋值到对象属性和取得对象属性的值用于持久化操作,对于更新操作,还需要检查对象的值是否已发生变化,即是否为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,
至于为什么,就能给大家去思考一些吧?

<script src="http://www.cchensoft.com/gad/blog_csdn_article.js" type=text/javascript></script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值