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