開發應用程序的過程當中,常常會對一些比較重要的數據修改都須要寫日誌。在實際工做的工程中,這些數據都是存在表中的, 一個常見的作法是用觸發器,在增刪改的時候,用觸發器將數據寫入到另外一張表中去,但我的不推薦這麼作,緣由以下:
1. 若是有多個表,得寫不少觸發器。
2. 觸發器與數據庫特性關聯太緊,不一樣的數據庫,雖然思路同樣,但語法卻不太同樣。
對數據庫表操做的日誌記錄,徹底能夠利用Hibernate的Interceptor特性來實現,也就是攔截器。下面用一個具體的例子來講明如何使用Hibernate的Interceptor。
建立一個表,用來記錄日誌的表 java
程序代碼spring
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;sql
建立這個表對應的實體類:數據庫
程序代碼session
@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;
}
.... 餘下部分能夠參考提供下載的源代碼.app
建立一個接口,全部實現了這個接口的實體類,都會寫日誌ide
程序代碼工具
package com.mkyong.interceptor;
//market interface
public interface IAuditLog {
public Long getId();
public String getLogDeatil();
}post
這裏有兩個方法,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 注入進去
程序代碼
<bean id="hibermateInterceptor" class="com.yihaomen.interceptor.AuditLogInterceptor"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref local="dataSource_jdbc"/>
</property>
<property name="entityInterceptor" ref="hibermateInterceptor"/>
..............
</bean>
這樣就能實現對整個項目中須要記錄日誌的實體類進行攔截,並記錄增刪改的日誌記錄. 仍是很方便的,重點就是 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