Hibernate Envers – Entity Auditing

Beim Entity Auditing geht es darum Veränderungen an unseren JPA-Objekten festzuhalten um Nachverfolgen zu können wie sich ein Datensatz im Laufe der Zeit verändert hat. Bereits seit der Hibernate Version 3.5 steht „Envers“ im HibernateCore-Module bereit und bietet Out-Of-The-Box eine eben solche Lösung.

Die Funktionsweise von Envers ist schnell erklärt: intern arbeitet Envers mit Listenern die während einem merge / persist / remove  aktiv werden und den Zustand vor der Aktion in einer separaten Tabelle festhalten. Dabei verwendet Envers so genannte Revisions, eine Art Versionsnummer für Entitäten die pro Transaktion generiert wird und anhand der die unterschiedlichen Stände der Entitäten ermittelt werden können.

Unsere hier gezeigten Beispiele zielen auf den Wildfly 10 als Laufzeitumgebung ab der die benötigen Bibliotheken bereits (in Version 5.0.7) mit bringt. Um Envers im Projekt ein setzen reicht demnach eine provided-Abhängigkeit in unserer pom.xml:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>5.0.7.Final</version>
    <scope>provided</scope>
</dependency>

Um nun ganze Entitäten oder wahlweise nur einzelne Attribute der Historisierung zu unterwerfen reicht es  mittels „@Audited“ Annotation dieses Hibernate mit zu teilen:

Auditing
@Entity
@Table(name = "ORDERTBL")
@Audited
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer orderID;

    @OneToMany(mappedBy = "order")
    @NotAudited
    private List<OrderDetail> orderDetails;

Das obige Beispiel zeigt die einfache Verwendung von Hibernate Envers. Mittels „@Audited“ markieren wir unsere Order-Entität für die Historisierung. Jegliche Transaktionen die Änderungen vornimmt führt nun zu einer neuen Revision. Dabei berücksichtigt Envers auch abhängige Objekte und würde diese, wenn nicht wie im obigen Beispiel mittels „@NotAudited“ annotiert, ebenfalls historisieren.

Neben der zu erwartenden Tabelle „ORDERTBL“ die unseren aktuellen Datensatz beinhaltet führt Envers nun eine weitere Tabelle mit dem default-Suffix „_AUD“ ein:

hibernate_envers

Hierbei handelt es sich um die Revisionstabelle für unsere Entität. Diese beinhaltet die Revisionnummer, einen Revisiontyp (ADD,MOD, DEL) und alle Daten der Entität, bzw. der Attribute die zur Historisierung markiert wurden. Eine entsprechende API zum Zugriff auf die Revisionsdaten stellt Envers ebenfalls bereit, dabei ist die Synatx sehr stark an die CriteriaAPI angelehnt.

Selektion der ersten Revision einer Bestellung mit bestimmter ID:

AuditReader auditReader = AuditReaderFactory.get(entityManager);
List<Number> revisions = auditReader.getRevisions(Order.class, order.getOrderID());
Order revOrder = auditReader.find(Order.class, order.getOrderID(), revisions.get(0));
Assert.assertTrue(revOrder.getShipName().equals(oldShipName));

Wer zum Teufel war das?

Eine häufige Anforderung ist ebenfalls das zu einer Revision zusätzliche Daten abgelegt werden sollen, z.B. das aktuelle Datum oder der Benutzer der die Änderung vollzogen hat. Hierzu bietet Envers ebenfalls eine Lösung. Zwei Klassen sind dazu zu implementieren

  • RevisonEntity
    • Entität welche die zusätzlichen Daten für jede Revision bereit hält
  • Revision Listener
    • Ermittlung der Daten
@Entity
@RevisionEntity(RevisionDataListener.class)
public class RevisionData extends DefaultRevisionEntity {

    @Temporal(TemporalType.TIMESTAMP)
    private Date changeDate;

    private String username;

    //Getter und Setter

}
public class RevisionDataListener implements RevisionListener {

    @Override
    public void newRevision(Object o) {
        RevisionData revData = (RevisionData) o;
        revData.setChangeDate(new Date());
        revData.setUsername(getUsername());
    }

    private String getUsername() {
    	return "some username"
    }

}

Jede Revision die erzeugt wird, durchläuft nun unserer Listener und wird mit den Informationen angereichert. Diese Informationen werden in einer separaten Tabelle mit entsprechender Referenz zur Revision in der Datenbank abgelegt und können mittels AuditReader ausgelesen werden:

hibernate_envers_revisiondata

// Revision-Objekt und zusätzliche Daten auf Basis der ID (8) und der Revision (55) ermitteln.
AuditQuery revQuery = auditReader.createQuery().forRevisionsOfEntity(Product.class, false, true);
revQuery.add(AuditEntity.revisionNumber().eq(55));
revQuery.add(AuditEntity.id().eq(8));

// liefert Objekt Array mit Entität, RevisionObjekt und RevisionType
Object[] revObject = (Object[]) revQuery.getSingleResult();

Product revProduct = (Product) revObject[0];
RevisionData revData = (RevisionData) revObject[1];
RevisionType revType = (RevisionType) revObject[2];

Fazit

Man sollte sich schon gut überlegen welche Daten wirklich dem Auditing unterworfen werden sollen, da dieser augenscheinlich so leichtgewichtige Ansatz natürlich bei schreibenden Operationen zu einem wesentlich höheren Aufwand auf Seiten der Datenbank führt. Davon abgesehen ist Envers eine sehr einfache Möglichkeit die Historisierung von Entitäten zu realisieren. Ein paar Annotationen reichen aus und Envers kümmert sich um den Rest.

Weite Informationen: Envers Docs

Github Projekt: https://github.com/GEDOPLAN/hibernate-envers

Advertisements

Über Dominik Mathmann
Dominik Mathmann arbeitet als Berater, Entwickler und Trainer bei der GEDOPLAN GmbH. Auf Basis seiner langjährigen Erfahrung in der Implementierung von Java-EE-Anwendungen leitet er Seminare, hält Vorträge und unterstützt Kunden bei der Konzeption und Realisierung von Webanwendungen vom Backend bis zum Frontend. Sein derzeitiger Schwerpunkt liegt dabei auf der Entwicklung von JSF-Anwendungen. Er sieht die stärker werdende Position von JavaScript-Frameworks jedoch positiv und beschäftigt sich darüber hinaus mit Webframeworks wie AngularJS.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: