Hibernateインターセプタの例 - 監査ログ

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;

すべての監査データがデータベースに挿入されます。

interceptor-example

結論

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

この例をダウンロード–Hibernate interceptor example.zip