Exemple d’intercepteur Hibernate - Journal d’audit

Exemple d'intercepteur Hibernate - journal d'audit

Hibernate possède une fonctionnalité puissante appelée «interceptor» pour intercepter ou accrocher différents types d'événements Hibernate, comme l'opération CRUD de base de données. Dans cet article, je vais montrer comment implémenter une fonctionnalité de journal d’audit d’applications à l’aide de l’intercepteur Hibernate, il consignera toutes les opérations d’enregistrement, de mise à jour ou de suppression d’Hibernate dans une table de base de données nommée «auditlog».

Exemple d'intercepteur Hibernate - journal d'audit

1. Créer une table

Créez une table appelée ‘auditlog’ pour stocker tous les enregistrements audités de l’application.

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. Créer une interface de marqueur

Créez une interface marqueur, toutes les classes qui ont implémenté cette interface seront auditées. Cette interface requiert que la classe implémentée expose son identifiant -getId() et le contenu à journaliser - «getLogDeatil()». Toutes les données exposées seront stockées dans la base de données.

package com.example.interceptor;
//market interface
public interface IAuditLog {

    public Long getId();
    public String getLogDeatil();
}

3. Mappez la table ‘auditlog’

Un fichier de modèle d'annotation normal à mapper avec la table «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. Une classe a implémenté IAuditLog

Un fichier de modèle d'annotation normal à mapper avec la table «stock», qui sera utilisé pour la démonstration d'intercepteur plus tard. Il doit implémenter l'interface de marqueurIAuditLog et implémenter les méthodesgetId() etgetLogDeatil().

...
@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. Créer une classe d'assistance

Une classe d'assistance pour accepter les données de l'intercepteur et les stocker dans la base de données.

...
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. Créer une classe d'intercepteur Hibernate

Créez une classe d'intercepteur en étendant lesEmptyInterceptor Hibernate. Voici la fonction d'intercepteur la plus populaire.

  • onSave - Appelé lorsque vous enregistrez un objet, l'objet n'est pas encore enregistré dans la base de données.

  • onFlushDirty - Appelé lorsque vous mettez à jour un objet, l'objet n'est pas encore mis à jour dans la base de données.

  • onDelete - Appelé lorsque vous supprimez un objet, l'objet n'est pas encore supprimé dans la base de données.

  • preFlush - Appelé avant que les objets enregistrés, mis à jour ou supprimés ne soient validés dans la base de données (généralement avant postFlush).

  • postFlush - Appelé après la validation des objets enregistrés, mis à jour ou supprimés dans la base de données.

Le code est assez verbeux, il devrait être auto-exploratoire.

...
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

Vous pouvez activer l'intercepteur en le transmettant comme argument à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();
    }
   }
...

En test d'insertion

session.saveOrUpdate(stockInsert); //it will call onSave
tx.commit(); // it will call preFlush follow by postFlush

En test de mise à jour

session.saveOrUpdate(stockUpdate); //it will call onFlushDirty
tx.commit(); // it will call preFlush follow by postFlush

En test de suppression

session.delete(stockUpdate); //it will call onDelete
tx.commit();  // it will call preFlush follow by postFlush
Sortie
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 (?, ?, ?, ?, ?)
Dans la base de données
SELECT * FROM auditlog a;

Toutes les données auditées sont insérées dans la base de données.

interceptor-example

Conclusion

Les journaux d'audit sont une fonctionnalité utile qui est souvent gérée dans la base de données à l'aide de déclencheurs, mais je recommanderais d'utiliser l'application pour l'implémenter pour des raisons de portabilité.

Téléchargez cet exemple -Hibernate interceptor example.zip