Hibernate4 拦截器(Interceptor) 实现实体类增删改的日志记录
By:Roy.LiuLast updated:2014-01-19
开发应用程序的过程中,经常会对一些比较重要的数据修改都需要写日志。在实际工作的工程中,这些数据都是存在表中的, 一个常见的做法是用触发器,在增删改的时候,用触发器将数据写入到另一张表中去,但个人不推荐这么做,原因如下:
1. 如果有多个表,得写很多触发器。
2. 触发器与数据库特性关联太紧,不同的数据库,虽然思路一样,但语法却不太一样。
对数据库表操作的日志记录,完全可以利用Hibernate的Interceptor特性来实现,也就是拦截器。下面用一个具体的例子来说明如何使用Hibernate的Interceptor。
创建一个表,用来记录日志的表
创建这个表对应的实体类:
创建一个接口,所有实现了这个接口的实体类,都会写日志
这里有两个方法,getId,getLogDetail 需要实现类去实现具体的方法,也就是要被写入到日志表中的详细记录.
创建一个类实现了IAuditLog 接口,并给出接口方法的具体实现
创建记录日志的工具类,所有写日志公用
创建 Hibernate interceptor 拦截器,这是重点,这里拦截所有需要记录日志的类,并处理
这里面有几个比较常用的方法:
onSave – 保存数据的时候调用,数据还没有保存到数据库.
onFlushDirty – 更新数据时调用,但数据还没有更新到数据库
onDelete – 删除时调用.
preFlush – 保存,删除,更新 在提交之前调用 (通常在 postFlush 之前).
postFlush – 提交之后调用(commit之后)
写测试例子, 添加数据,更新数据,然后再删掉
运行结果如下:
另外查看 auditLog 这张表, 可以看到日志成功写入
另外,如果是在SPRING 容器中使用,应该将这个interceptor 注入进去
这样就能实现对整个项目中需要记录日志的实体类进行拦截,并记录增删改的日志记录. 还是很方便的,重点就是 Hibernate interceptor 的使用.
测试在是在 Hibernate 4.3 下测试的, 如果是hibernate 3 在openSession的时候是不同的,hibernate4用了session = HibernateUtil.getSessionFactory().withOptions().interceptor(interceptor).openSession(); 如果是Hibernate 3的话,应该是:session = HibernateUtil.getSessionFactory().openSession(interceptor);。
项目代码下载:
Hibernate4 interceptor audit log sample download
1. 如果有多个表,得写很多触发器。
2. 触发器与数据库特性关联太紧,不同的数据库,虽然思路一样,但语法却不太一样。
对数据库表操作的日志记录,完全可以利用Hibernate的Interceptor特性来实现,也就是拦截器。下面用一个具体的例子来说明如何使用Hibernate的Interceptor。
创建一个表,用来记录日志的表
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;
创建这个表对应的实体类:
@Entity @Table(name = "auditlog") public class AuditLog implements java.io.Serializable { private Long auditLogId; private String action; private String detail; private Date createdDate; private long entityId; private String entityName; public AuditLog() { } public AuditLog(String action, String detail, Date createdDate, long entityId, String entityName) { this.action = action; this.detail = detail; this.createdDate = createdDate; this.entityId = entityId; this.entityName = entityName; } @Id @GeneratedValue(strategy = IDENTITY) @Column(name = "AUDIT_LOG_ID", unique = true, nullable = false) public Long getAuditLogId() { return this.auditLogId; } .... 余下部分可以参考提供下载的源代码.
创建一个接口,所有实现了这个接口的实体类,都会写日志
package com.mkyong.interceptor; //market interface public interface IAuditLog { public Long getId(); public String getLogDeatil(); }
这里有两个方法,getId,getLogDetail 需要实现类去实现具体的方法,也就是要被写入到日志表中的详细记录.
创建一个类实现了IAuditLog 接口,并给出接口方法的具体实现
@Entity @Table(name="stock") public class Stock implements java.io.Serializable,IAuditLog { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="STOCK_ID") private Integer stockId; @Column(name="STOCK_CODE", length=10) private String stockCode; @Column(name="STOCK_NAME", length=20) private String stockName; public Stock() { } public Stock(String stockCode, String stockName) { this.stockCode = stockCode; this.stockName = stockName; } ....省略部分getter,setter。 @Transient public Long getId(){ return this.stockId.longValue(); } @Transient public String getLogDeatil(){ StringBuilder sb = new StringBuilder(); sb.append(" Stock Id : ").append(stockId) .append(" Stock Code : ").append(stockCode) .append(" Stock Name : ").append(stockName); return sb.toString(); } }
创建记录日志的工具类,所有写日志公用
public class AuditLogUtil{ public static void LogIt(String action, IAuditLog entity){ Session tempSession = HibernateUtil.getSessionFactory().openSession(); try { tempSession.getTransaction().begin(); AuditLog auditRecord = new AuditLog(action,entity.getLogDeatil() , new Date(),entity.getId(), entity.getClass().toString()); tempSession.save(auditRecord); tempSession.getTransaction().commit(); } finally { tempSession.close(); } } }
创建 Hibernate interceptor 拦截器,这是重点,这里拦截所有需要记录日志的类,并处理
public class AuditLogInterceptor extends EmptyInterceptor{ 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) { System.out.println("execute sql: " + sql); return super.onPrepareStatement(sql); } public boolean onSave(Object entity,Serializable id, Object[] state,String[] propertyNames,Type[] types) throws CallbackException { System.out.println("onSave"); if (entity instanceof IAuditLog){ inserts.add(entity); } return false; } public boolean onFlushDirty(Object entity,Serializable id, Object[] currentState,Object[] previousState, String[] propertyNames,Type[] types) throws CallbackException { System.out.println("onFlushDirty"); if (entity instanceof IAuditLog){ updates.add(entity); } return false; } public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { System.out.println("onDelete"); if (entity instanceof IAuditLog){ deletes.add(entity); } } //called before commit into database public void preFlush(Iterator iterator) { System.out.println("preFlush"); } //called after committed into database public void postFlush(Iterator iterator) { System.out.println("postFlush"); try{ for (Iterator it = inserts.iterator(); it.hasNext();) { IAuditLog entity = (IAuditLog) it.next(); System.out.println("postFlush - insert"); AuditLogUtil.LogIt("Saved",entity); } for (Iterator it = updates.iterator(); it.hasNext();) { IAuditLog entity = (IAuditLog) it.next(); System.out.println("postFlush - update"); AuditLogUtil.LogIt("Updated",entity); } for (Iterator it = deletes.iterator(); it.hasNext();) { IAuditLog entity = (IAuditLog) it.next(); System.out.println("postFlush - delete"); AuditLogUtil.LogIt("Deleted",entity); } } finally { inserts.clear(); updates.clear(); deletes.clear(); } } }
这里面有几个比较常用的方法:
onSave – 保存数据的时候调用,数据还没有保存到数据库.
onFlushDirty – 更新数据时调用,但数据还没有更新到数据库
onDelete – 删除时调用.
preFlush – 保存,删除,更新 在提交之前调用 (通常在 postFlush 之前).
postFlush – 提交之后调用(commit之后)
写测试例子, 添加数据,更新数据,然后再删掉
public class App { public static void main(String[] args) { Session session = null; Transaction tx = null; try { AuditLogInterceptor interceptor = new AuditLogInterceptor(); session = HibernateUtil.getSessionFactory().withOptions().interceptor(interceptor).openSession(); //session = HibernateUtil.getSessionFactory().openSession(); //interceptor.setSession(session); //test insert tx = session.beginTransaction(); Stock stockInsert = new Stock(); stockInsert.setStockCode("1111"); stockInsert.setStockName("yihaomen"); session.saveOrUpdate(stockInsert); tx.commit(); //test update tx = session.beginTransaction(); Query query = session.createQuery("from Stock where stockCode = '1111'"); Stock stockUpdate = (Stock)query.list().get(0); stockUpdate.setStockName("yihaomen-update"); session.saveOrUpdate(stockUpdate); tx.commit(); //test delete tx = session.beginTransaction(); session.delete(stockUpdate); tx.commit(); } catch (RuntimeException e) { try { tx.rollback(); } catch (RuntimeException rbe) { // log.error("Couldn抰 roll back transaction", rbe); } throw e; } finally { if (session != null) { session.close(); } } } }
运行结果如下:
onSave execute sql: insert into stock (STOCK_CODE, STOCK_NAME) values (?, ?) Hibernate: insert into stock (STOCK_CODE, STOCK_NAME) values (?, ?) preFlush postFlush postFlush - insert Hibernate: insert into auditlog (ACTION, CreateD_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) values (?, ?, ?, ?, ?) preFlush execute sql: select stock0_.STOCK_ID as STOCK_ID1_1_, stock0_.STOCK_CODE as STOCK_CO2_1_, stock0_.STOCK_NAME as STOCK_NA3_1_ from stock stock0_ where stock0_.STOCK_CODE='1111' Hibernate: select stock0_.STOCK_ID as STOCK_ID1_1_, stock0_.STOCK_CODE as STOCK_CO2_1_, stock0_.STOCK_NAME as STOCK_NA3_1_ from stock stock0_ where stock0_.STOCK_CODE='1111' preFlush onFlushDirty execute sql: update stock set STOCK_CODE=?, STOCK_NAME=? where STOCK_ID=? Hibernate: update stock set STOCK_CODE=?, STOCK_NAME=? where STOCK_ID=? postFlush postFlush - update Hibernate: insert into auditlog (ACTION, CreateD_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) values (?, ?, ?, ?, ?) onDelete preFlush execute sql: delete from stock where STOCK_ID=? Hibernate: delete from stock where STOCK_ID=? postFlush postFlush - delete Hibernate: insert into auditlog (ACTION, CreateD_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) values (?, ?, ?, ?, ?)
另外查看 auditLog 这张表, 可以看到日志成功写入
另外,如果是在SPRING 容器中使用,应该将这个interceptor 注入进去
..............
这样就能实现对整个项目中需要记录日志的实体类进行拦截,并记录增删改的日志记录. 还是很方便的,重点就是 Hibernate interceptor 的使用.
测试在是在 Hibernate 4.3 下测试的, 如果是hibernate 3 在openSession的时候是不同的,hibernate4用了session = HibernateUtil.getSessionFactory().withOptions().interceptor(interceptor).openSession(); 如果是Hibernate 3的话,应该是:session = HibernateUtil.getSessionFactory().openSession(interceptor);。
项目代码下载:
Hibernate4 interceptor audit log sample download
From:一号门
Previous:Hiberante4 原生SQL查询 例子
COMMENTS