Alle Beiträge, Technologien & Entwicklung

Hibernate beyond OR-Mapping: Envers

Beitragsbild zu Hibernate beyond OR Mapping

Hibernate ist als OR-Mapping-Framework seit Jahren fest in der Java-Welt (und darüber hinaus, siehe NHibernate ) etabliert. Weniger bekannt sind allerdings die zusätzlich enthaltenen Module, mit denen Hibernate dem Entwickler auch über die OR-Mapping-Thematik hinaus das Leben einfacher machen kann.

Nachdem sich der erste Beitrag der “Hibernate beyond OR-Mapping” Serie mit dem Thema “Suche” anhand von Hibernate Search beschäftigte, soll hier nun die Versionierung von Geschäftsdaten mittels Hibernate Envers das Thema sein.

Envers erweitert Hibernate  um die Historisierung von Entity Beans. Damit lassen sich frühere Bearbeitungsstände (Revisionen) abfragen und Änderung nachvollziehen (wer hat wann was gemacht). Seit der Hibernate-Version 3.5 ist Envers Bestandteil des Core-Moduls. Im  jStage Backendsystem “Stagemanager” wird es für die Historisierung besonders geschäftskritischer Daten, wie z. B. Artikelmengendefinitionen und Benutzerrechte, eingesetzt.

Unter Envers erfolgt die Versionierung transparent beim Speichern der entsprechenden Entitäten ohne zusätzlichen Boilerplate-Code. Zudem wird das Auslesen der Historientabellen durch entsprechende API-Methoden unterstützt. Somit bietet der Einsatz von Envers erhebliche Produktivitätsvorteile gegenüber selbstgestrickten Implementierungen.

Historisierung

Sehen wir uns nun die Historisierung anhand eines Beispiels an. Historisierte Entitäten werden mit der Annotation @Audited gekennzeichnet. Damit werden auch automatisch alle Properties und Assoziationen in die Historie aufgenommen. Falls man bestimmte Properties ausschließen will, muss man die Ausnahmen mit @NotAudited kennzeichnen.

Im folgenden Beispiel historisieren wir die Entität Item die einen Artikel im Online-Shop repräsentieren soll. Dabei wollen wir nicht alle Daten in unserer Historie haben, sondern uns nur auf die wichtigsten Eigenschaften beschränken. Die Artikelnummer wird initial bei der Anlage vergeben und danach nicht mehr geändert. Der Preis unterliegt Änderungen, daher wollen wir ihn in die Historie aufnehmen. Die Kategoriezuordnungen sind ebenfalls Änderungen unterworfen, z. B. kann ein Artikel im Laufe seines Lebenszyklus von der Kategorie “Neuheiten” in die Kategorie “Abverkauf” wandern. Wir sind für unsere Historie allerdings nur an Änderungen der Kategoriezuordnung interessiert, nicht an der Historisierung der Kategorie selbst. Daher wird die Assozation categories mit RelationTargetAuditMode.NOT_AUDITED versehen.

Grundlegend für das Verständnis von Hibernate Envers ist das Konzept der Revision. Eine Transaktion, die den Zustand einer Entität ändert, erzeugt eine Revision (ähnlich wie z. B. im Versionsverwaltungssystem Subversion). Die Revisionen werden von Envers in Audit-Tabellen geschrieben, die mit dem Kürzel _AUD am Ende des Tabellennamens gekennzeichnet sind. Diese Namenskonvention kann per Konfiguration geändert werden, für die weiteren Beispiele bleiben wir aber bei der Standardkonvention.  Für die oben dargestellte Entität Item generiert Envers zwei Audit-Tabellen: ITEM_AUD und CATEGORY_ITEM_AUD. Zudem benötigt Envers die Tabelle REVINFO, welche die Metainformationen zu den Revisionen speichert. Standardmäßig sind das die Revisions-Id und der Zeitpunkt der Änderung als Zeitstempel. Es ist auch möglich, eigene Daten in REVINFO abzulegen, wie wir später noch sehen werden.

Sehen wir uns nun die Revisionstabellen anhand einer Beispieltransaktion genauer an. Wir verändern dazu Preis und Kategoriezuordnung eines Items.  Zunächst definieren wir eine Methode updateItem, welche diese Änderungen durchführt.

Diese Methode rufen wir wie folgt auf:

Nach dem Methodenaufruf zeigen die Revisionstabellen den folgenden Zustand:

REVINFO

ID TIMESTAMP
1 1401718370317

In REVINFO ist bereits erkennbar, dass eine Revision mit der Id 1 und einem Zeitstempel erzeugt wurde.

ITEM_AUD

ITEM_ID REV REVTYPE PRICE
23 1 1 8.99

Hier werden die Referenzen auf das geänderte Item und die Revision, in der die Änderung verursacht wurde abgespeichert. Zudem wird der neue Preis hier abgelegt.
Die Spalte REVTYPE gibt den Typ der Änderung an. 1 bedeutet hier, dass ein bestehender Datensatz geändert wurde. Weitere mögliche Werte sind 0 für Neuanlagen und 2 für Löschungen.

CATEGORY_ITEM_AUD

CATEGORY_ID ITEM_ID REV REVTYPE
42 23 1 0

Ähnlich sieht es in CATEGORY_ITEM_AUD aus. Auch hier haben wir Referenzen auf die Revision und den betroffenen Datensatz. Allerdings hat der Eintrag hier den Revtype 0, da der entsprechende Datensatz in CATEGORY_ITEM neu angelegt wurde.

Abfragen der Historie

Um Zugriff auf die Historieneinträge zu erhalten, bietet Envers das Interface AuditReader an.

Damit lassen sich z. B. alle Revisionen eines bestimmten Items ermitteln.

Man kann auch gezielt nur nach Revisionen suchen, in denen eine bestimmte Eigenschaft geändert wurde, z. B. der Preis.

Zudem lassen sich die Revisionen auch über Zeiträume selektieren.

Zusätzliche Daten in der Revision speichern

In der Tabelle REVINFO lassen sich auch eigene Zusatzinformationen zu den Revisionen ablegen. Von besonderem Interesse kann z. B. der Benutzer sein, der die Änderung durchgeführt hat. Die Tabelle kann erweitert werden, indem man eine eigene Klasse von der darauf gemappten DefaultRevisionEntity ableitet. Im folgenden Beispiel erweitern wir diese Entity um die Account-Id des Benutzers.

Innerhalb der Transaktion kann über den AuditReader auf die aktuelle Revision zugegriffen und die AccountId gesetzt werden. Die oben dargestellte Methode updateItem könnte mit der erweiterten Protokollierung z. B. wie folgt aussehen.

Der Boolean-Parameter von getCurrentRevision gibt an, ob die Revision angelegt werden soll, falls sie zum Zeitpunkt der Abfrage noch nicht existiert. Davon ist in der Regel abzuraten, es sei denn man ist sich sicher, dass die Transaktion zu diesem Zeitpunkt bereits relevante Daten verändert hat.

Fazit

In diesem Blog Post wurde ein kurzer Einblick in Hibernate Envers gegeben. Es wurde erläutert, wie die Erzeugung und Abfrage von Revisionen grundsätzlich funktioniert und wie die Historie um eigene Metadaten ergänzt werden kann. Natürlich steckt in dem Thema viel mehr, als hier in diesem knappen Rahmen vermittelt werden kann. Für weiterführende Informationen sei auf die offizielle Dokumentation verwiesen.

Weitere Beiträge der Serie

Teil 1: Hibernate beyond OR-Mapping: Hibernate Search
Teil 2: Hibernate beyond OR-Mapping: Envers