NHibernate Event/Listener 的设定 - 对象的创建与修改跟踪审计

如果你从 NHibernate 缓存设定测试项目 下载过那个测试项目源码,可能注意到里面有一部分内容是做对象变化跟踪的。或者你可能发现这个内容根本不能正确工作!别急,今天我们就着手解决这个问题。

首先我把 nhtest 这个项目升级到了 nhibernate 3.2.0-GA,基本上没有什么大变动,不过有些配置文件和动态库不再需要了,被剔除的支持库包括:

Antlr3.Runtime.dll
LinFu.DynamicProxy.dll
NHibernate.ByteCode.LinFu.dll

(原文链接 http://ddbiz.com/?p=105)

(至于 proxy 是否能发挥作用,不是本文的重点,后续文章再讨论),更新后的项目文件就是现在【这个】了

关于对象变化的跟踪设定,网上有很多的文章,我也参考了很多(自己一点一点来确实太累了),不过这些内容似乎都不能正确工作。经过必要的“探索”,得出一点儿结论。咱们先把结论放在这:

【结论】

1. 如果在 hibernate.cfg.xml 配置文件中定义 <event type=""><listener ... /></event> 或者<listener ... />,那么这个listener 将替换掉NHibernate中的那些DefaultxxxxxListener,这将导致一些功能不能正确实现。

2. 如果持久化对象采用 dynamic-update="true"的方式,那么IPreUpdateEventListener.OnPreUpdate 将不被触发。

3. 区别对待对象的创建、修改等保存的跟踪审计。

接下来,让我们详细了解一下 NHibernate 3.2 中Event的情况吧。当我们不对 nhibernate 3.2.0进行任何而外的event/listener设定时(包括xml 的配置文件或者编程方式),它将默认装载一些欲定义的event。通过一段初始化代码:

                cfg = new Configuration();
                cfg.Configure();
                //DynamicListener listeners = new DynamicListener();
                //listeners.Register(cfg);
                StringBuilder sb = new StringBuilder();
                NHibernate.Event.EventListeners els = cfg.EventListeners;
                foreach (PropertyInfo pi in els.GetType().GetProperties())
                {
                    sb.AppendFormat("{0}: ", pi.PropertyType.Name);
                    Array ao = (Array)pi.GetValue(els, null);
                    foreach (object o in ao)
                    {
                        sb.AppendFormat("{0} ", o.GetType().Name);
                    }
                    sb.AppendLine();
                    
                }
                Logger.Info(sb.ToString());   

我们可以查看到这些与定义的event有那些:

ILoadEventListener[]: DefaultLoadEventListener 
ISaveOrUpdateEventListener[]: DefaultSaveOrUpdateEventListener 
IMergeEventListener[]: DefaultMergeEventListener 
IPersistEventListener[]: DefaultPersistEventListener 
IPersistEventListener[]: DefaultPersistOnFlushEventListener 
IReplicateEventListener[]: DefaultReplicateEventListener 
IDeleteEventListener[]: DefaultDeleteEventListener 
IAutoFlushEventListener[]: DefaultAutoFlushEventListener 
IDirtyCheckEventListener[]: DefaultDirtyCheckEventListener 
IFlushEventListener[]: DefaultFlushEventListener 
IEvictEventListener[]: DefaultEvictEventListener 
ILockEventListener[]: DefaultLockEventListener 
IRefreshEventListener[]: DefaultRefreshEventListener 
IFlushEntityEventListener[]: DefaultFlushEntityEventListener 
IInitializeCollectionEventListener[]: DefaultInitializeCollectionEventListener 
IPostLoadEventListener[]: DefaultPostLoadEventListener 
IPreLoadEventListener[]: DefaultPreLoadEventListener 
IPreDeleteEventListener[]: 
IPreUpdateEventListener[]: 
IPreInsertEventListener[]: 
IPostDeleteEventListener[]: 
IPostUpdateEventListener[]: 
IPostInsertEventListener[]: 
IPostDeleteEventListener[]: 
IPostUpdateEventListener[]: 
IPostInsertEventListener[]: 
ISaveOrUpdateEventListener[]: DefaultSaveEventListener 
ISaveOrUpdateEventListener[]: DefaultUpdateEventListener 
IMergeEventListener[]: DefaultSaveOrUpdateCopyEventListener 
IPreCollectionRecreateEventListener[]: 
IPostCollectionRecreateEventListener[]: 
IPreCollectionRemoveEventListener[]: 
IPostCollectionRemoveEventListener[]: 
IPreCollectionUpdateEventListener[]: 
IPostCollectionUpdateEventListener[]: 
( 表1)

nhibernate 3.2.0 默认加载了20个预定义的event,也就是NHibernate.Event.Default下所有的DefaultxxxxEventListener都被用上了!这些预设的Event将保证NHibernate在Get/Load/Save/Update/Evict/...等等操作中能够被正确执行,恰恰也正因为如此,如果我们想要使用【结论1】的方式配置event/listener时,如果涉及到的listener是Default已经定义过的,那么请把默认的Listener也配置进去,比如 IFlushEntityEventListener:

    <event type='flush-entity'>
      <listener type='flush-entity' class='ddbiz.nhtest.listener.DynamicListener, ddbiz.nhtest' />
      <listener type='flush-entity' class='NHibernate.Event.Default.DefaultFlushEntityEventListener' />
    </event>

当然,为了避免配置文件中遗忘加载默认的Listener, 可以直接从Defaultxxxx中继承并实现自己的Listener,比如:

    public class DynamicListener : DefaultFlushEntityEventListener, IPreUpdateEventListener, IPreInsertEventListener
    {
        #region IPreUpdateEventListener 成员
        #endregion
        
	#region IPreInsertEventListener 成员
        #endregion
        
        #region DefaultFlushEntityEventListener

        protected override void DirtyCheck(FlushEntityEvent e)
        {
            base.DirtyCheck(e);
            if (e.DirtyProperties != null &&
                e.DirtyProperties.Any() &&
                e.Entity is IAuditObject &&
                Array.IndexOf(e.EntityEntry.Persister.PropertyNames, "UpdateTimestamp") > -1)
            {
                e.DirtyProperties = e.DirtyProperties.Concat(AuditObjectProperties(e)).ToArray();
            }
        }

        private IEnumerable<int> AuditObjectProperties(FlushEntityEvent e)
        {
            IAuditObject ao = e.Entity as IAuditObject;
            if (ao != null )
                ao.UpdateTimestamp = e.Session.Factory.OpenStatelessSession().GetNamedQuery("CurrentTimestamp").UniqueResult<DateTime>();
            yield return
                Array.IndexOf(@e.EntityEntry.Persister.PropertyNames, "UpdateTimestamp");

        }
        #endregion
    }

    <event type='flush-entity'>
      <listener type='flush-entity' class='ddbiz.nhtest.listener.DynamicListener, ddbiz.nhtest' />
    </event>

要实现诸如跟踪一个对象的创建、更改的变化,完全没有想象中的那么简单。一个对象的 Save/Update 过程,受到很多因素的影响,比如:对象键值的生成方式、逻辑层采用用的Save, Update还是 SaveOrUpdate.为了能够详细的理解这些变化,我们特别对其进行了跟踪:

0. 准备工作

这里我们仅仅考虑了最简单的跟踪方式:记录对象创建的时间,最近的一次修改时间。在实际应用中,这个审计功能是完全不能达到要求的。

    public interface IAuditObject
    {
        /// <summary>
        /// 创建时间
        /// </summary>
        DateTime CreateTimestamp { get; set; }
        /// <summary>
        /// 更新时间
        /// </summary>
        DateTime UpdateTimestamp { get; set; }
    }

需要被审计跟踪的对象,都继承自 IAuditObject 

1. 创建对象,并跟踪一个对象的创建时间

当创建并保存(持久化)对象时,我们可以采用

ISession.Save()
ISession.SaveOrUpdate()

两种方式持久一个对象时,都将触发 OnSaveOrUpadte事件:

ISession.Save() --> FireSave(SaveOrUpdateEvent) --> listeners.SaveEventListeners[].OnSaveOrUpdate()

ISession.SaveOrUpdate() --> FireSaveOrUpdate(SaveOrUpdateEvent) -->listeners.SaveOrUpdateListener[].OnSaveOrUpdate()

2. 更改对象,并跟踪一个对象的变更时间

当保存一个修改后的对象时,我们可以采用

ISession.Update();

ISession.SaveOrUpdate()

这两种保存方式,也都会触发OnSaveOrUpdate事件

ISession.Update() --> FireUpdate(SaveOrUpdateEvent) --> listeners.UpdateEventListeners[].OnSaveOrUpdate()

ISession.SaveOrUpdate() --> FireSaveOrUpdate(SaveOrUpdateEvent) -->listeners.SaveOrUpdateListener[].OnSaveOrUpdate()

到这里,我们可以看到,NH中有3个重要的和保存对象有关的Event/Listener:
SaveEventListener
UpdateEventListener
SaveOrUpdateEventListener
根据 表1 的记载,NH对这3个事件都有默认的事件实现,并且 SaveEventListener和 UpdateEventListener都是从 SaveOrUpdateEventListener中继承的


3. 何时跟踪审计一个对象的变化

常见的说法是:

   PreInsertEvent
   PreUpdateEvent
   FlushEntityEvent

可以帮助我们跟踪审计一个对象,但是并不建议在 PreInsert/PreUpdate 中对对象进行变更(如给CreateTimestamp/UpdateTimestamp赋值)。最好的审计方式应该在 FlushEntity事件中完成。实际应用中基本上都会符合这种讲法,但是不完全正确。下面是一个精简的对象保存流程:

using (ServiceFactory sf = new ServiceFactory())
{
                TProxy p = new TProxy("localhost", 80) { CnnType = "http", Country = "CN", Flow = 0, Status = TProxyStatus.New };
                //sf.Session.Persist(p);
                ITransaction tx = sf.Session.BeginTransaction();
                sf.Session.SaveOrUpdate(p);
                tx.Commit();

                tx = sf.Session.BeginTransaction();
                p.Flow = 100;
                sf.Session.Update(p);
                tx.Commit();
}

通过对NH的跟踪我们可以发现这样一个保存的流程:

Session.SaveOrUpdate(obj) --> OnSaveOrUpdate() --> OnFlushEntity()

当我们调用SaveOrUpdate(obj)或者Save(obj)保存/持久一个对象时,首先触发的是 OnSaveOrUpdate()事件,它可以在NH的配置文件中定义,如:

    <event type='save-update'>
      <listener type='save-update' class='ddbiz.nhtest.service.listener.ControlListener, ddbiz.nhtest'/>
      <listener type='save-update' class='NHibernate.Event.Default.DefaultSaveOrUpdateEventListener'/>
    </event>

并且:如果对象的Id是数据库的表的自增字段,OnSaveOrUpdate将在触发之后,OnFlushEntity之前就执行一个Insert into table的操作,从而获得这个对象的持久化Id。换句话说,对于依靠自增字段来设置对象Id的对象,应该在自定义的OnSaveOrUpdate之中设置对象的生成时间。这也是此类对象的唯一可记录创建时间的地方。

尽管 ISession.Save和 ISession.SaveOrUpdate都能触发 OnSaveOrUpdate,也必须在配置中明确声明这些配置,如:

   <event type='save'>
      <listener type='save' class='ddbiz.nhtest.service.listener.ControlListener, ddbiz.nhtest'/>
      <listener type='save' class='NHibernate.Event.Default.DefaultSaveEventListener'/>
    </event>
   
    <event type='save-update'>
      <listener type='save-update' class='ddbiz.nhtest.service.listener.ControlListener, ddbiz.nhtest'/>
      <listener type='save-update' class='NHibernate.Event.Default.DefaultSaveOrUpdateEventListener'/>
    </event>

    <event type='flush-entity'>
      <listener type="flush-entity" class='ddbiz.nhtest.service.listener.ControlListener, ddbiz.nhtest'/>
      <listener type='flush-entity' class='NHibernate.Event.Default.DefaultFlushEntityEventListener' />
    </event>


可以在OnFlushEntity 中跟踪审计对象的变化,比如 UpdateTimestamp。
再做结论:

使用 上面那个配置,分别定义 event 

type=save, 
type=save-update, 
type-flush-entity

三个事件,其中前两个用来跟踪对象的创建、后一个用来跟踪对象的修改。

(原文链接 http://ddbiz.com/?p=105)

你可能感兴趣的:(NHibernate Event/Listener 的设定 - 对象的创建与修改跟踪审计)