Hibernate4 拦截器(Interceptor) 实现实体类增删改的日志记录

前言

开发应用程序的过程中,经常会对一些比较重要的数据修改都需要写日志。在实际工作的工程中,这些数据都是存在表中的, 一个常见的做法是用触发器,在增删改的时候,用触发器将数据写入到另一张表中去,但个人不推荐这么做,原因如下:

1. 如果有多个表,得写很多触发器。
2. 触发器与数据库特性关联太紧,不同的数据库,虽然思路一样,但语法却不太一样。
对数据库表操作的日志记录,完全可以利用Hibernate的Interceptor特性来实现,也就是拦截器。下面用一个具体的例子来说明如何使用Hibernate的Interceptor。

1、创建一个表,用来记录日志的表 

CREATE TABLE `auditlog` (
	`AUDIT_LOG_ID` BIGINT (20) UNSIGNED NOT NULL AUTO_INCREMENT,
	`ACTION` VARCHAR (100) NOT NULL,
	`DETAIL` text NOT NULL,
	`CreateD_DATE` DATE NOT NULL,
	`ENTITY_ID` BIGINT (20) UNSIGNED NOT NULL,
	`ENTITY_NAME` VARCHAR (255) NOT NULL,
	PRIMARY KEY (`AUDIT_LOG_ID`)
) ENGINE = INNODB AUTO_INCREMENT = 9 DEFAULT CHARSET = utf8;

2、创建这个表对应的实体类:

@Entity
@Table(name = "core_db.sys_audit_log")
public class AuditLog extends BaseEntity {

    private static final long serialVersionUID = 3538696750590772955L;

    private String auditLogId;

    private String action;

    private String detail;

    private String entityId;

    private String entityName;

    public AuditLog() {
    }

    public AuditLog(String action, String detail, String entityId, String entityName) {
        this.action = action;
        this.detail = detail;
        this.entityId = entityId;
        this.entityName = entityName;
    }

    public AuditLog(String auditLogId, String action, String detail, String entityId, String entityName) {
        super();
        this.auditLogId = auditLogId;
        this.action = action;
        this.detail = detail;
        this.entityId = entityId;
        this.entityName = entityName;
    }

    @Id
    @GenericGenerator(name = "systemUUID", strategy = "uuid")
    @GeneratedValue(generator = "systemUUID")
    @Column(name = "audit_id", unique = true, nullable = false)
    public String getAuditLogId() {
        return this.auditLogId;
    }

    public void setAuditLogId(String auditLogId) {
        this.auditLogId = auditLogId;
    }

    ......getter and setter 省略

}

3、创建一个接口,所有实现了这个接口的实体类,都会写日志

public interface IAuditLog {
    public String getId();
    public String getLogDeatil();
}
这里有两个方法,getId,getLogDetail 需要实现类去实现具体的方法,也就是要被写入到日志表中的详细记录.

4、创建一个类实现了IAuditLog 接口,并给出接口方法的具体实现

@Entity
@Table(name = "core_db.sys_right")
public class Right extends BaseEntity implements IAuditLog {

    private static final long serialVersionUID = -6438614246840973733L;

    /** 主键 */
    private String rightId;

    /** 权限名称 */
    @UpdateAnnotation
    private String rightName;

    /** 权限类型 */
    @UpdateAnnotation
    private String rightType;

    /** 权限备注 */
    @UpdateAnnotation
    private String rightRemark;

    @Id
    @GenericGenerator(name = "generator", strategy = "assigned")
    @GeneratedValue(generator = "generator")
    @Column(name = "right_id")
    public String getRightId() {
        return rightId;
    }

    public void setRightId(String rightId) {
        this.rightId = rightId;
    }

    ......省略部分 setter and getter

    @Transient
    @Override
    public String getPrimaryKey() {
        return this.rightId;
    }

    @Transient
    @Override
    public String getLogDeatil() {
        StringBuilder sb = new StringBuilder();
        sb.append(" Right Id : ").append(rightId).append(" Right Name : ").append(rightName);
        return sb.toString();
    }

}
5、创建记录日志的工具类,所有写日志公用 

public class AuditLogUtil {

    /**
     * 数据库操作日志
     * 
     * @param action
     * @param entity
     * @param token
     */
    public static void LogAuditIt(String action, IAuditLog entity, String token) {
        AuditLog auditRecord = new AuditLog(action, entity.getLogDeatil(), entity.getPrimaryKey(), entity.getClass()
                .toString());
        // 日志单线程
        AuditLogThread auditLogThread = new AuditLogThread(auditRecord, token);
        CacheProjectInfo.getInstance().getAuditLogPoolExecutor().execute(auditLogThread);
    }

}

// 创建记录日志的线程
public class AuditLogThread implements Runnable {

    private AuditLog auditLog;

    private String token;

    public AuditLogThread(AuditLog auditLog, String token) {
        this.auditLog = auditLog;
        this.token = token;
    }

    public void run() {
        TokenManager.getCurrHashMap().put(Thread.currentThread(), token);
        // 保存日志到数据库
        LogService logService = (LogService) CacheProjectInfo.getInstance().getApplicationContext()
                .getBean("logServiceImpl");
        logService.addAuditLog(auditLog);
    }
}

// 程序启动时创建线程池
public class ServletOnStart extends HttpServlet {

    private static final Logger LOGGER = LoggerFactory.getLogger(ServletOnStart.class);

    private static final long serialVersionUID = 5494158583746262904L;

    /**
     * 通过web方式,系统初始化,启动相应的服务
     */
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        // 加载顺序不能改变
        CacheProjectInfo projectInfo = CacheProjectInfo.getInstance();

        // SPRING 信息保存
        WebApplicationContext applicationContext = WebApplicationContextUtils
                .getWebApplicationContext(servletConfig.getServletContext());
        projectInfo.setApplicationContext(applicationContext);// 设置SPRING属性

        // 日志线程池
        ThreadPoolExecutor auditLogPoolExecutor = new ThreadPoolExecutor(1, 1, 5, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(10000), new ThreadPoolExecutor.DiscardPolicy());// 不能执行的任务将被删除
        projectInfo.setAuditLogPoolExecutor(auditLogPoolExecutor);// 加入线程池

    }
}
6、创建 Hibernate interceptor 拦截器,这是重点,这里拦截所有需要记录日志的类,并处理

@SuppressWarnings({"rawtypes", "unchecked"})
public class AuditLogInterceptor extends EmptyInterceptor {

    @Resource
    protected LoginCacheService loginCache;

    private static final long serialVersionUID = 2723788204258441665L;

    Session session;

    private Set inserts = new HashSet();

    private Set updates = new HashSet();

    private Set deletes = new HashSet();

    public void setSession(Session session) {
        this.session = session;
    }

    @Override
    public String onPrepareStatement(String sql) {
        return super.onPrepareStatement(sql);
    }

    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types)
            throws CallbackException {
        if (entity instanceof IAuditLog) {
            inserts.add(entity);
        }
        String token = TokenManager.getCurrHashMap().get(Thread.currentThread());
        // 无需token的操作-如果token为空则设置默认值
        if (StringUtils.isEmpty(token)) {
            for (int i = 0; i < propertyNames.length; i++) {
                if ("createInsId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = "C000000";
                }
                if ("createUserId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = "0";
                }
                if ("updateUserId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = "0";
                }
                if ("flag".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = 1;
                }
            }
            return true;
        }
        LoginUser loginVo = (LoginUser) loginCache.getLogin(token);
        // 超时或token已不存在
        if (loginVo == null) {
            for (int i = 0; i < propertyNames.length; i++) {
                if ("createInsId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = "C000000";
                }
                if ("createUserId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = "0";
                }
                if ("updateUserId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = "0";
                }
                if ("flag".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = 1;
                }
            }
            return true;
        } else {
            // 后台用户
            for (int i = 0; i < propertyNames.length; i++) {
                if ("createInsId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = loginVo.getUserVo().getCreateInsId();
                }
                if ("createUserId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = loginVo.getUserVo().getUserId();
                }
                if ("updateUserId".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = loginVo.getUserVo().getUserId();
                }
                if ("flag".equals(propertyNames[i]) && null == state[i]) {
                    state[i] = 1;
                }
            }
            return true;

        }
    }

    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
            String[] propertyNames, Type[] types) throws CallbackException {
        if (entity instanceof IAuditLog) {
            updates.add(entity);
        }
        return false;
    }

    public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (entity instanceof IAuditLog) {
            deletes.add(entity);
        }
    }

    // called before commit into database
    public void preFlush(Iterator iterator) {
    }

    // called after committed into database
    public void postFlush(Iterator iterator) {
        try {
            String token = TokenManager.getCurrHashMap().get(Thread.currentThread());
            for (Iterator it = inserts.iterator(); it.hasNext();) {
                IAuditLog entity = (IAuditLog) it.next();
                AuditLogUtil .LogAuditIt("Saved", entity, token);
            }
            for (Iterator it = updates.iterator(); it.hasNext();) {
                IAuditLog entity = (IAuditLog) it.next();
                AuditLogUtil .LogAuditIt("Updated", entity, token);
            }
            for (Iterator it = deletes.iterator(); it.hasNext();) {
                IAuditLog entity = (IAuditLog) it.next();
                AuditLogUtil .LogAuditIt("Deleted", entity, token);
            }
        } finally {
            inserts.clear();
            updates.clear();
            deletes.clear();
        }
    }

}
这里面有几个比较常用的方法:

onSave – 保存数据的时候调用,数据还没有保存到数据库.
onFlushDirty – 更新数据时调用,但数据还没有更新到数据库
onDelete – 删除时调用.
preFlush – 保存,删除,更新 在提交之前调用 (通常在 postFlush 之前).
postFlush – 提交之后调用(commit之后)

注意:如果是在SPRING 容器中使用,应该将这个interceptor 注入进去

<bean id="coreInstitutionInterceptor" class="com.mnt.database.interceptor.AuditLogInterceptor" />

<!-- core-db配置 -->
<bean id="coreSessionFactory"
	class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
	<property name="configLocation" value="classpath:hibernate-core-mysql.cfg.xml" />
	<property name="entityInterceptor" ref="coreInstitutionInterceptor" />
</bean>
这样就能实现对整个项目中需要记录日志的实体类进行拦截,并记录增删改的日志记录. 还是很方便的,重点就是 Hibernate interceptor 的使用.

测试在是在 Hibernate 4.3 下测试的, 如果是hibernate 3 在openSession的时候是不同的

// hibernate 4
session = HibernateUtil.getSessionFactory().withOptions().interceptor(interceptor).openSession(); 
// Hibernate 3
session = HibernateUtil.getSessionFactory().openSession(interceptor);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值