Hibernateインターセプターの例–監査ログ
Hibernateには、データベースCRUD操作など、さまざまな種類のHibernateイベントをインターセプトまたはフックするための「interceptor」と呼ばれる強力な機能があります。 この記事では、Hibernateインターセプターを使用してアプリケーション監査ログ機能を実装する方法を示します。Hibernateのすべての保存、更新、または削除操作を「auditlog」という名前のデータベーステーブルに記録します。
Hibernateインターセプターの例–監査ログ
1. テーブルを作成する
「auditlog」というテーブルを作成して、すべてのアプリケーション監査済みレコードを保存します。
DROP TABLE IF EXISTS `example`.`auditlog`; CREATE TABLE `example`.`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. マーカーインターフェイスを作成する
マーカーインターフェイスを作成します。このインターフェイスを実装したクラスはすべて監査されます。 このインターフェースでは、実装されたクラスが識別子–getId()とログに記録するコンテンツ– ‘getLogDeatil()‘を公開する必要があります。 すべての公開データはデータベースに保存されます。
package com.example.interceptor;
//market interface
public interface IAuditLog {
public Long getId();
public String getLogDeatil();
}
3. 「監査ログ」テーブルをマッピングする
テーブル「auditlog」でマップする通常の注釈モデルファイル。
@Entity
@Table(name = "auditlog", catalog = "example")
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;
...
}
4. IAuditLogを実装したクラス
後でインターセプターのデモに使用するテーブル「stock」でマップする通常の注釈モデルファイル。 IAuditLogマーカーインターフェイスを実装し、getId()およびgetLogDeatil()メソッドを実装する必要があります。
...
@Entity
@Table(name = "stock", catalog = "example"
public class Stock implements java.io.Serializable, IAuditLog {
...
@Transient
@Override
public Long getId(){
return this.stockId.longValue();
}
@Transient
@Override
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();
}
...
5. ヘルパークラスを作成する
インターセプターからデータを受け取り、データベースに保存するヘルパークラス。
...
public class AuditLogUtil{
public static void LogIt(String action,
IAuditLog entity, Connection conn ){
Session tempSession = HibernateUtil.getSessionFactory().openSession(conn);
try {
AuditLog auditRecord = new AuditLog(action,entity.getLogDeatil()
, new Date(),entity.getId(), entity.getClass().toString());
tempSession.save(auditRecord);
tempSession.flush();
} finally {
tempSession.close();
}
}
}
6. Hibernateインターセプタークラスを作成する
HibernateEmptyInterceptorを拡張して、インターセプタークラスを作成します。 最も一般的なインターセプター関数は次のとおりです。
-
onSave –オブジェクトを保存するときに呼び出されますが、オブジェクトはまだデータベースに保存されていません。
-
onFlushDirty –オブジェクトを更新するときに呼び出されますが、オブジェクトはまだデータベースに更新されていません。
-
onDelete –オブジェクトを削除するときに呼び出されますが、オブジェクトはまだデータベースに削除されていません。
-
preFlush –保存、更新、または削除されたオブジェクトがデータベースにコミットされる前に呼び出されます(通常はpostFlushの前に)。
-
postFlush –保存、更新、または削除されたオブジェクトがデータベースにコミットされた後に呼び出されます。
コードは非常に冗長であり、自己探求する必要があります。
...
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;
}
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, session.connection());
}
for (Iterator it = updates.iterator(); it.hasNext();) {
IAuditLog entity = (IAuditLog) it.next();
System.out.println("postFlush - update");
AuditLogUtil.LogIt("Updated",entity, session.connection());
}
for (Iterator it = deletes.iterator(); it.hasNext();) {
IAuditLog entity = (IAuditLog) it.next();
System.out.println("postFlush - delete");
AuditLogUtil.LogIt("Deleted",entity, session.connection());
}
} finally {
inserts.clear();
updates.clear();
deletes.clear();
}
}
}
7.Enabling the interceptor
インターセプターを引数としてopenSession(interceptor);に渡すことにより、インターセプターを有効にできます。
...
Session session = null;
Transaction tx = null;
try {
AuditLogInterceptor interceptor = new AuditLogInterceptor();
session = HibernateUtil.getSessionFactory().openSession(interceptor);
interceptor.setSession(session);
//test insert
tx = session.beginTransaction();
Stock stockInsert = new Stock();
stockInsert.setStockCode("1111");
stockInsert.setStockName("example");
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("example-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’t roll back transaction", rbe);
}
throw e;
} finally {
if (session != null) {
session.close();
}
}
...
挿入テスト
session.saveOrUpdate(stockInsert); //it will call onSave tx.commit(); // it will call preFlush follow by postFlush
更新テスト中
session.saveOrUpdate(stockUpdate); //it will call onFlushDirty tx.commit(); // it will call preFlush follow by postFlush
削除テストで
session.delete(stockUpdate); //it will call onDelete tx.commit(); // it will call preFlush follow by postFlush
出力
onSave
Hibernate:
insert into example.stock
(STOCK_CODE, STOCK_NAME)
values (?, ?)
preFlush
postFlush
postFlush - insert
Hibernate:
insert into example.auditlog
(ACTION, CREATED_DATE, DETAIL, ENTITY_ID, ENTITY_NAME)
values (?, ?, ?, ?, ?)
preFlush
Hibernate:
select ...
from example.stock stock0_
where stock0_.STOCK_CODE='1111'
preFlush
onFlushDirty
Hibernate:
update example.stock
set STOCK_CODE=?, STOCK_NAME=?
where STOCK_ID=?
postFlush
postFlush - update
Hibernate:
insert into example.auditlog
(ACTION, CREATED_DATE, DETAIL, ENTITY_ID, ENTITY_NAME)
values (?, ?, ?, ?, ?)
onDelete
preFlush
Hibernate:
delete from example.stock where STOCK_ID=?
postFlush
postFlush - delete
Hibernate:
insert into example.auditlog
(ACTION, CREATED_DATE, DETAIL, ENTITY_ID, ENTITY_NAME)
values (?, ?, ?, ?, ?)
データベース内
SELECT * FROM auditlog a;
すべての監査データがデータベースに挿入されます。

結論
監査ログは、トリガーを使用してデータベースで処理されることが多い便利な機能ですが、移植性を考慮して、アプリケーションを使用して実装することをお勧めします。
この例をダウンロード–Hibernate interceptor example.zip