Jan Pacek
10.4.2012

DB žurnál pomocí Hibernate interceptoru



Na mnoha projektech je třeba řešit databázový žurnál aplikace, který zaznamenává události v systému včetně dat, která při těchto událostech byla změněna či jen čtena. Zákazníci často požadují u událostí, které mění data, žurnálovat jak staré tak i nové hodnoty záznamu. Implementace takového mechanismu přímo na DAO vrstvě by byla pracná a hlavně složitě konfigurovatelná. Řešení žurnálování na úrovni databázových procedur a triggerů by zase zrušilo nezávislost aplikace na konkrétním typu databáze. Hibernate pro žurnálování nabízí zajímavou funkčnost interceptor pro zachycení a zpracování různých událostí, jako například databázové CRUD operace. 

Vycházím z příkladu použití interceptoru na audit log (žurnál) a samozřejmě ze samotné dokumentace http://docs.jboss.org/hibernate/core/3.3/reference/en/html/events.html Po menší modifikaci lze uvedený příklad použít i na žurnálování starých a nových hodnot. Vytvořímě si entitu AuditLogData pro ukládání změněných hodnot a přidáme ji do entity AuditLog.

@Entity @Table(name = "auditlog", catalog = "mkyong")
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;
  @OneToMany() @JoinColumn (name = "auditLogId") @OrderBy("propertyName") privateList auditLogData;
  ... 
}

 

@Entity @Table(name = "auditlogdata", catalog = "mkyong")
public class AuditLogData implements java.io.Serializable {
  private Long auditLogDataId;
  private String propertyName;
  private String oldValue;
  private String newValue;
  ... 
}

 

Při ukládání záznamu budeme pracovat přímo se záznamem AuditLog třídy a ne pouze s entity.

public boolean onFlushDirty(Object entity,Serializable id, Object[] currentState,Object[] previousState, String[] propertyNames,Type[] types) throws CallbackException {
  ... 
  if (entity instanceof IAuditLog){
    AuditLog auditRecord = new AuditLog("Updated“,entity.getLogDeatil(), new Date(),entity.getId(), entity.getClass().toString());
    fillChangedData(auditRecord, previousState, currentState, propertyNames, entity);
    //Add the auditLog to be saved as update after commit of transaction 
    updates.add(auditRecord);
  } 
  return false;
}

 Kde plnění změněných dat by mohlo vypadat přibližně následovně:

private void fillChangedData(AuditLog auditLog, Object[] startState, Object[] endState, String[] propertyNames, Object newObject, Long persistedObjectId) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
  //check input parameters …
  for (int i = 0; i < propertyNames.length; i++) {
    //check if the property has been changed
    if (propertyValueChanged(startState[i], endState[i])) {
      // property changed, add it to auditLog.auditLogData list
      final String pn = propertyNames[i];
      String valueOld = propertyValueToString(startState[i]);
      String valueNew = propertyValueToString(endState[i]);
      addDataToAuditLog(auditLog, pn, valueOld, valueNew);
    }
  }
}

 

Jak je na příkladu vidět, Hibernate interceptor je mohutný nástroj, který umožňuje na aplikační úrovni nejen odchytávat operace nad databází, ale i dále zpracovávat jejich data.

Vaše emailová adresa nebude zveřejněna

Komentáře

Děkujeme za váš komentář
Další
  • Petr Stahl

    Nám se pro podobnou funkčnost osvědčil Hibernate Envers. http://www.jboss.org/envers

  • Jan Pacek

    Díky za zajímavý tip

  • Arnost Valicek

    Používate/zkoušeji jste jak se interceptor chová při použití Batch update? http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/batch.html#batch-direct Zdá se že Hibernate umí provést update přímo, bez toho aby načítal všechny objekty do paměti. Ale to by asi mohlo rozbít audit postavený na interceptoru?

    1. Jan Pacek

      Zatím jsme to v aplikaci se žurnálováním nezkoušeli, ale předpokládám, že pokud by někdo danou akci pomocí Batch update chtěl zažurnálovat, tak by se muselo vkládání záznamu do žurnálu ošetřit už na dao vrstvě bez použití Hibernate interceptoru a bez zažurnálování staré hodnoty záznamu/ů. Záleží co je silnějším požadavkem na aplikaci, zda rychlost (batch update je samozřejmě rychlejší), anebo detailní žurnál aplikace(ne vše je nutné žurnálovat), který je samozřejmě pomalejší (při každém uložení, update, či i načtení záznamu se provede navíc vložení záznamu/ů do žurnálu)