回调与拦截机制
在某些情况下,我们需要对实体的
CURD
操作进行捕获并执行一些操作,这可以通过数据库触发器来实现,但是正如我们上一节中所分析的,由于触发器的执行对
Hibernate Session
是透明的,因此会带来很多问题(参见上一节)。为此
Hibernate
提供了一些专门用于捕获监听实体
CURD
操作的接口,通过这些接口可以实现类似触发器的功能,能够在实体发生
CURD
操作时捕获事件,并且执行相应的动作逻辑。在
Hibernate
中这些接口是:
Lifecycle,Validatable,Interceptor,
下面我们就分别讲解怎样通过这些接口,实现回调拦截的技术细节。
A、
Lifecycle
与
Validatable
在
Hibernate
中
Lifecycle
接口定义如下:
public interface Lifecycle{
/**
在实体对象执行
save/insert
操作之前触发
**/
public boolean onSave(Session session) throws CallbackException;
/**
在
session.update()
执行之前触发
**/
public boolean onUpdate(Session session) throws CallbackException;
/**
在实体对象执行
delete
操作之前触发
**/
public boolean onDelete(Session session) throws CallbackException;
/**
在实体对象加载之后触发
**/
public void onLoad(Session session) throws CallbackException;
}
实体对象可以实现
Lifecycle
接口,来获得在持久化阶段捕获
CURD
事件,并执行相应动作的能如下所示:
public class User implements Serializable,Lifecycle{
public boolean onSave(Session s) throws CallbackException{
……
return false;
……
}
public boolean onUpdate(Session s) throws CallbackException{
……
return true;
……
}
public boolean onDelete(Session s) throws CallbackException{
……
return false;
……
}
public boolean onLoad(Session s) throws CallbackException{
……
}
}
对于
onSave,onUpdate,onDelete
方法,如果返回
true
则意味着需要终止执行对应的操作过程。如果在运行时抛出
CallbackException
,对应的操作也会被终止。
注意在接口中对应的方法中,不要去通过方法的
Session
参数执行持久化操作,在这些方法中
Session
无法正常使用,如果必须要执行一些持久化操作,那么需要进行特殊的处理,我们将在
Interceptor
部分详细讲解。
Hibernate
中还定义了
Validatable
接口,该接口定义如下:
public interface Validatable{
public void validate() throws ValidationFailure;
}
Validatable
接口是用来实现数据验证的,实体类实现
Validatable
接口,并在接口的
validate
方法中实现数据验证逻辑,以保证数据输入的合法性。
validate
方法将会在实体对象持久化前得到调用进行数据验证,与
Lifecycle
接口中的方法不同,
Validatable.validate()
方法在实体生命周期中可能被多次调用,因此此方法应该仅限于数据合法性的验证,而不应该实现业务逻辑的验证。
B
、
Interceptor:
以上是
Hibernate
提供的
Lifecycle
接口和
Validatable
接口,以及使用方法,这两个方法定义了一种自然的回调机制,但是如我们所见,如果想实现对实体的回调拦截,那么相应的实体对象必须实现这两个
Hibernate
原生接口,这就使代码的可移植性大大下降,因为此时实体类已经不再是一个
POJO
了,
Hibernate
的那些天才的设计者们也已经意识到了这个问题,所以又提供了
Interceptor
接口,为持久化事件的捕获和处理提供了一个非入侵性的解决方案,
Interceptor
接口通过设置注入来实现持久化事件的捕获和处理,这是典型的
IOC
(控制反转)设计思想。下面我们就讲解
Interceptor
接口的技术细节和使用方法。
Hibernate
中的
Interceptor
接口定义如下:
public interface Interceptor{
//
对象初始化之前调用,这时实体对象刚刚被创建,各个属性还都为
null,
如果在这个方法中修改了实体对象的数据,那么返回
true
,否则返回
null.
public boolean onLoad(Object entity,Serializable id,Object[] state,
String[] propertyNames,Type[] types) throws CallbackException;
//Session.flush()
在进行脏数据检查时,如果发现实体对象数据已脏,就调用此方法
public boolean onFlushDirty(Object entity,Serializable id,Object[] state,
String[] propertyNames,Type[] types) throws CallbackException;
//
实体对象被保存前调用,如果在这个方法中修改了实体对象的数据,那么返回
true
,否则返回
null.
public boolean onSave(Object entity,Serializable id,Object[] state,
String[] propertyNames,Type[] types) throws CallbackException;
//
通过
Session
删除一个实体对象前调用
public boolean onDelete(Object entity,Serializable id,Object[] state,
String[] propertyNames,Type[] types) throws CallbackException;
//Session
执行
flush()
之前调用
public boolean preFlush(Iterator entities) throws CallbackException;
//Session
执行
flush()
之后,所有的
SQL
语句都执行完毕后调用
public boolean postFlush(Iterator entities) throws CallbackException;
//
当执行
saveOrUpdate
方法时调用,判断实体对象是否已经保存
public Boolean isUnsaved(Object entity);
//
执行
Session.flush()
方法时,调用此方法判断该对象是否为脏对象,这提供了脏数据检查的另一个回调拦截机制
public int[] findDirty(Object entity,Serializable id,Object[] state,
String[] propertyNames,Type[] types) throws CallbackException;
//
当
Session
构造实体类实例前调用,如果返回
null,Hibernate
会按照默认方式构造实体类对象实例
public Object findDirty(Class clazz,Serializable id) throws CallbackException;
}
Intercepter
不需要实体对象来实现,而是通过开发人员定义一个实现
Interceptor
接口的类,然后在创建
Hibernate Session
时,通过将
Interceptor
对象设置进所创建的
Session
,这样通过这个
Session
来操作的实体对象,就都会具有对持久化动作的回调拦截能力。在
Hibernate
中
Interceptor
对象共有两种用法,如下所述:
1、
SessionFactory.openSession(Interceptor)
:为每个
Session
实例分配一个拦截
Interceptor
,这个拦截接口对象,存放在
Session
范围内,为每个
Session
实例所专用。
2、
Configuration.setInterceptor(Interceptor):
为
SessionFactory
实例分配一个
Interceptor
实例,这个
Interceptor
实例存放在
SessionFactory
范围内,被每个
Session
实例所共享。
A、
Interceptor
的典型应用:
下面我实现一个利用
Interceptor
接口实现日志数据稽核的功能,所谓日志数据稽核就是针对一些关键操作进行记录,以便作为业务跟踪的基础依据。
首先定义用于记录操作的实体:
public class AudiLog implements Serializable{
private String id;
private String user;
private String action;
private String entityName;
private String comment;
private Long logtime;
…getter/setter…
}
接下来定义
Interceptor
接口的实现类和用于持久化操作的
AuditDAO
类:
package com.lbs.apps.unemployment.subsidy.beforeinfoimport.util;
import net.sf.hibernate.Session;
import net.sf.hibernate.Interceptor;
import java.io.Serializable;
import net.sf.hibernate.type.Type;
import net.sf.hibernate.HibernateException;
import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;
import com.neusoft.entity.User;
public class MyInterceptor implements Interceptor{
private Set insertset=new HashSet();
private Set updateset=new HashSet();
private Session session;
private String userID;
public void setSession(Session session){
this.session=session
}
public void setUserID(String id){
this.userID=id;
}
public boolean onLoad(Object object, Serializable serializable,
Object[] objectArray, String[] stringArray,
Type[] typeArray) {
return false;
}
public boolean onFlushDirty(Object object, Serializable serializable,
Object[] objectArray, Object[] objectArray3,
String[] stringArray, Type[] typeArray) {
if(object instanceof User){
insertset.add(object);
}
return false;
}
public boolean onSave(Object object, Serializable serializable,
Object[] objectArray, String[] stringArray,
Type[] typeArray) {
if(object instanceof User){
updateset.add(object);
}
return false;
}
public void onDelete(Object object, Serializable serializable,
Object[] objectArray, String[] stringArray,
Type[] typeArray) {
}
public void preFlush(Iterator iterator) {
}
public void postFlush(Iterator iterator) {
try{
if(insertset.size()>0){
AuditDAO.dolog(“insert”,userID,inserset,session.connection);
}
if(updateset.size()>0){
AuditDAO.dolog(“update”,userID,updateset,session.connection);
}
}catch(HibernateException he){
he.printStackTrace();
}
}
public Boolean isUnsaved(Object object) {
return null;
}
public int[] findDirty(Object object, Serializable serializable,
Object[] objectArray, Object[] objectArray3,
String[] stringArray, Type[] typeArray) {
return null;
}
public Object instantiate(Class class0, Serializable serializable) {
return "";
}
}
public class AuditDAO{
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()){
User user=(User)it.next();
AudiLog log=new AudiLog();
log.setUserID(userID);
log.setAction(action);
log.setComment(user.toString());
log.setLogTime(new Long(Calendar.getInstance().getTime().getTime()));
tempsession.save(log);
}
}catch(Exception e){
throw new CallbackException(e);
}finally{
try{
tempsesson.close();
}catch(HibernateException he){
throw new CallbackException(he);
}
}
}
}
最后看一下业务逻辑主程序:
SessionFactory sessionfactory=config.buildSessionFactory();
MyInterceptor it=new MyInterceptor();
session=sessionfactory().openSession(it);
it.setUserID(“currentUser”);
it.setSession(session);
User user=new User();
user.setName(“zx”);
Transaction tx=session.beginTransaction();
session.save(user);
tx.commit();
session.close();
以上示例代码中,在创建
Session
时,设置
Interceptor
实例对象,当执行到
session.save(user)
前,会触发
onSave()
方法,当执行
tx.commit()
时,会执行
flush()
,在执行该方法后会触发
postFlush()
方法,这个方法通过
AuditDAO
进行持久化保存业务日志,在这个类中的红色部分时有关持久化操作部分,我们并没有使用原有的
Session
实例,这是因为要避免
Session
内部状态混乱,因此我们依托当前
Session
的
JDBC Connection
创建了一个临时
Session
用于保存操作记录,在这个持久化操作中没有启动事务,这是因为临时
Session
中的
JDBC Connection
是与外围调用
Interceptor
的
Session
共享
,
而事务已经在外围
Session
的
JDBC Connection
上启动。这是在拦截方法中进行持久化操作的标准方法。总之
Interceptor
提供了非入侵性的回调拦截机制,使我们可以方便而且优雅的实现一些持久化操作的特殊需求。