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

来源:互联网 发布:淘宝长款半身裙 编辑:程序博客网 时间:2024/06/06 19:15

前言

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

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 4session = HibernateUtil.getSessionFactory().withOptions().interceptor(interceptor).openSession(); // Hibernate 3session = HibernateUtil.getSessionFactory().openSession(interceptor);

0 0
原创粉丝点击