1. Lifecycle与Validatable

在某些情况下,我们需要对实体对象的CRUD操作进行捕获并执行相应的处理。在数据库层,这通常通过触发器(Triger)实现。
Hibernate 通过Lifecycle、Validatable接口制定了实体对象CRUD过程中的回调(CallBack)方式。
Lifecycle.java

public interface Lifecycle {
    /**在实体对象Save/Insert操作之前触发*/
    public boolean onSave(Session s) throws CallbackException;
    /**在Session.update()操作之前触发*/
    public boolean onUpdate(Session s)throws CallbackException;
    /**在实体对象Delete操作之前触发*/
    public boolean onDelete(Session s)throws CallbackException;
    /**在实体对象被加载之后触发*/
    public void onLoad(Session s, Serializable id);
}

实体类通过实现Lifecycle接口,即可在特定的持久化阶段,触发特定的处理过程:

pulic class TUser implements Serializable,Lifecycle {
    …
    public boolean onSave(Session s)throws CallbackException {
        …
        return false;//insert操作正常执行
        …
    }
    public boolean onUpdate(Session s)throws CallbackException{
        …
        if(…)return true;//update操作将被终止
    }
    public boolean onDelete(Session s)throws CallbackException{
        …
        return false;//delete操作正常执行
    }
    public void onLoad(Session s, Serialiable id){
        …
    }
}

对于onSave、onUpdate、onDelete方法,如果返回true则意味着需要中止执行对应的操作过程。如果代码运行期间抛出了CallbackException,对应的操作也会被中止。

注:不要试图在这些方法中调用Session进行持久化操作,这些方法中Session无法正常使用。如果必须进行持久化操作,需要进行一些特殊处理,具体参见稍后关于Interceptor。

Validatable.java

public interface Validatable{
    public void validate() throws ValidationFailure;
}

Validatable接口定义了数据验证实现方式。实体类实现Validatable接口,并在validate对当前待保存的数据进行验证,以保证数据逻辑合法。

Validatable.validate方法将在实体被持久化之前得到调用以对数据进行验证。但注意,与Lifecycle的回调方法不同,此方法在实体对象的生命周期内可能被数次调用,因此,此方法应仅用于数据本身的逻辑校验,而不要试图在此实现业务逻辑的验证。

Lifecycle/Validatable接口定义了一种自然的回调机制。但是,这种机制要求实体类必须实现Lifecycle/Validatable接口,Hibernate原生接口的介入,使得我们的实体类移植性大大降低(实际上,此时的实体类已经不再是一个严格意义上的POJO)。
由于注意到这点,Hibernate引入了Interceptor,为持久化事件的捕获和处理提供了一个非侵略性的实现。

Interceptor

Interceptor接口定义了Hibernate中的通用拦截机制。Session创建时即可以指定加载相应的Interceptor,之后,此Session的持久化操作工作都将首先经由此拦截器捕获处理。

Interceptor.java

public interface Interceptor{
    //对象初始化之前加载,这里的entity处于刚被创建的状态(也就是说属性均未赋值)
    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException;
    //Session.flush方法进行脏数据检查时,如果发现PO状态改变,则调用此方法
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames,  Type[] types) throws CallbackException;
    //将在对象被保存之前调用,这提供了一个对待保存对象属性进行修改的机会
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException;
    //将在对象被删除之前调用
    public boolean onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException;
    //Session执行flush方法之前被调用
    public void preFlush(Iterator entities) throws CallbackException;
    //Session执行flush方法之后被调用
    public void postFlush(Iterator entities) throws CallbackException;
    //Session.saveOrUpdate方法时,将调用此方法判断对象是否尚未保存
    public Boolean isUnsaved(Object entity);
    //Session.flush方法时,将调用此方法判断对象是否为脏数据,这提供了脏数据检查的另一个拦截式实现渠道
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types);
    //用于创建实体对象时,如果返回null,则Hibernate将调用实体类的默认构造方法创建实体对象
    public Object instantiate(Class clazz, Serializable id) throws CallbackException;
}

另外值得注意的是,与Lifecycle相同,Interceptor的方法中不可通过Session实例进行持久化操作。关于Interceptor中的持久化实现参见“Interceptor典型应用”中的内容。

在创建Session实例时,我们可以通过编码加载Interceptor:

SessionFactory sessionFactory = cofig.buildSessionFactory();
Interceptor it = new MyInterceptor();
session = sessionFactory.openSession(it);

此后,此Session的所有动作均会被此Interceptor捕获。
假设MyInterceptor的实现如下:

public class MyInterceptor implements Interceptor{
    …
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException{
        if(entity instanceof TUser){
            System.out.println(“User to be saved=>”+((TUser)entity).getName());
        }
        return false;
    }
    …
}

执行:

SessionFactory sessionFactory = cofig.buildSessionFactory();
Interceptor it = new MyInterceptor();
session = sessionFactory.openSession(it);
TUser user = new TUser();
user.setName(“Erica”);
Transaction tx= session.beginTransaction();
session.save(user);
tx.commit();
session.close();

观察屏幕日志:

可以看到,MyInterceptor.onSave方法在Hibernate执行库表insert操作之前调用。

通过这个例子可以发现,Interceptor实际上覆盖了Lifecycle接口的功能,且具备更少的侵入性(实体类无需与任何Hibernate原生接口绑定)。


Interceptor典型应用

“数据稽核”功能是不少关键系统的必备功能。
何谓“数据稽核”?即针对关键信息及其变更历史进行审查,作为业务跟踪的基础依据。
那么如何实现关键信息更新操作的记录。显然这并不是个非常困难的技术问题。最常见的方式就是在各个业务逻辑单元中通过编码进行记录。
回顾上面关于Interceptor的探讨,我们可以发现,对于基于Hibernate的应用而言,通过Interceptor实现数据稽核功能也许是最自然的一种方式。
以t_user表操作为例,我们来看看基于Interceptor的实现方式。
Interceptor接口中,与数据更新操作相关的拦截方法有onSave和onFlushDirty。其中onSave方法对应insert操作,onFlushDirty对应update操作。
那么,我们只要针对这两个操作实现对应的拦截器方法,并在此方法中对更新历史进行保存,那么所有关于t_user表的修改操作都将被记录在案。
这种实现方式通过集中化的记录机制,避免了各业务逻辑单元中通过编码进行操作记录的可能缺漏,提供了最为全面的数据跟踪机制。另一方面,也避免了稽核逻辑在业务逻辑中分散,最大程度上避免了重复代码的出现。
假设“历史记录”实体机构如下:

public class TAuditLog implements Serializable{
    private Integer id;
    private String user;
    private String action;
    private String entityName;
    private String comment;
    private Long logtime;
    …
}

对应的Interceptor实现如下:

public class MyInterceptor implements Interceptor{
    private Session session;
    private String userID;
    private Set insertSet = new HashSet();
    private Set updateSet = new HashSet();
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException{
        if(entity instanceof TUser){
            insertSet.add(entity);
        }
        return false;
    }
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException{
        if(entity instanceof TUser){
            updateSet.add(entity);
        }
        return false;
    }
    public void postFlush(Iterator entities) throws CallbackException{
        try{
            if(insertSet.size()>0){
                AuditDAO.doLog(“insert”, userID, insertSet, session.connection());
            }
            if(updateSet.size()>0){
                AutitDAO.doLog(“update”, userID,  updateSet, session.connection());
            }
        }catch(HibernateException e){
            e.printStackTrace();
        }
    }
    …
}

可以看到,在onSave、onFlushDirty方法中,我们对发生改变的数据对象进行了记录,并在postFlush方法中将所有这些操作记录到库表中。
对应的Session级代码如下:

SessionFactory sessionFactory = cofig.buildSessionFactory();
MyInterceptor it = new MyInterceptor();
session = sessionFactory.openSession(it);
it.setSession(session);
it.setUserID(“CurrentUser”);
TUser user = new TUser();
user.setName(“Erica”);
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
session.close();

AuditDAO.doLog方法在这里实现了操作日志记录的功能,但我们注意到,这里并没有直接利用当前的session,而是转而利用了当前Session的JDBC Connection引用。session.connection()

之前曾说过Lifecycle和Interceptor中都不能调用当前Session进行操作,Lifecycle和Interceptor接口中定义的方法,都将由当前Session负责调用,而如果在这些方法中又调用了当前Session进行持久化操作,则将导致Session内部状态的混乱。

很容易得到的一个思路就是在AuditDAO中获取一个新的Session实例完成持久化操作。显然这个思路逻辑上完全正确,但是从性能方面考虑,一个操作需要两个Session完成,也就意味着需要同时独占两个数据库连接,这对并发量较大的系统来说可能有点奢侈。
既然当前Session实例无法重用。重用当前Session的数据库连接多少能减少一点性能损耗。
于是有了以下代码:

public static void doLog(String action, String userID, set modifySet, Connection connection){
    Session tempSession =
HibernateUtil.getSessionFactory.openSession(connection);
    try{
        Iterator it = modifySet.iterator();
        while(it.hasNext()){
            TUser user = (TUser)it.next();
            TAuditLog auditLog = new TAuditLog();
            auditLog.setUserID(userID);
            auditLog.setAction(action);
            auditLog.setComment(user.toString());
            auditLog.setLogTime(getCurrentTime());
            tempSession.save(auditLog);
        }
        tempSession.flush();
    }catch(Exception ex){
        throw new CallbackException(ex);
    }finally{
        try{
            tempSession.close();
        }catch(HibernateException ex){
            throw new CallbackException(ex);
        }
    }
    …
}

这样,通过SessionFactory.openSession(Connection dbconn)方法,依托当前Session的JDBC Connection,我们创建了一个临时Session实例用于保存操作记录。
值得注意的是,在AuditDAO.doLog方法里,我们无需再启动事务,原因就在于临时Session中的JDBC Connection是与执行Interceptor的Session所共享的,而在此JDBCConnection上事务已经在外围逻辑代码中启动。
同样的道理,由于共享JDBCConnection,我们也无需执行close操作。
Interceptor提供了非侵入性的拦截器实现机制。基于这种机制,我们可以以更加灵活的方式实现传统数据逻辑中的一些特殊需求。在实际开发中,我们可以根据自己的需求进行相应的调整。
在Hibernate3中,提供了基于时间监听Listener机制的拦截器实现。这种方式可以提供比Interceptor更加清晰合理的实现模型。由于实现方法大同小异,这里不再赘述,大家可自行评判。