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

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

上一篇: Hiberante4 原生SQL查询 例子
下一篇: Hibernate 使用log4j,sl4j 记录日志并记录sql 语句参数的值
 评论 ( What Do You Think )
名称
邮箱
网址
评论
验证
   
 

 


  • 微信公众号

  • 我的微信

站点声明:

1、一号门博客CMS,由Python, MySQL, Nginx, Wsgi 强力驱动

2、部分文章来源于互联网, 若有侵权, 联系邮箱:summer@yihaomen.com, 同时欢迎大家注册用户,主动发布文章.

3、鄂ICP备14001754号-3, 鄂公网安备 42280202422812号