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