Auditierung mit Hibernate Envers – eine Einführung

Das Speichern aller Datenbankänderungen ist seit Jahren eine feste Anforderung an Anwendungssysteme. Es verwundert daher nicht, dass es dafür inzwischen eine Vielzahl erprobter Implementierungskonzepte gibt. Hibernate Envers bietet neben der automatischen Auditierung aller Datenänderungen eine funktionsreiche API zum Zugriff auf die auditierten Daten.

In Pocket speichern vorlesen Druckansicht 4 Kommentare lesen
Auditierung mit Hibernate Envers – eine Einführung
Lesezeit: 14 Min.
Von
  • Thorben Janssen
Inhaltsverzeichnis

Hibernate Envers ist Teil des Hibernate-Kernprojekts und integriert sich automatisch in Hibernate ORM. Dadurch lässt sich die Auditierung von Änderungen an Entitäten unter Verwendung einer Standardkonfiguration mit wenigen Schritten aktivieren.

Zuerst ist Hibernate Envers zum aktuellen Projekt hinzuzufügen. Dazu wird lediglich die Datei hibernate-envers.jar im Klassenpfad der Anwendung benötigt. Diese erhält man zum Beispiel unter den folgenden Maven-Koordinaten:

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.2.10.Final</version>
</dependency>

Als Nächstes sind die Datenbanktabellen zu erstellen, in denen die auditierten Daten gespeichert werden sollen. Dazu benötigt man eine Revisions- und eine Audittabelle für jede zu auditierende Tabelle. Hibernate Envers speichert in der Revisionstabelle REVINFO einen neuen Datensatz für jede Transaktion, innerhalb der Änderungen an auditierten Entitäten durchgeführt wurden:

CREATE TABLE 
rev integer NOT NULL,
revtstmp bigint,
CONSTRAINT revinfo_pkey PRIMARY KEY (rev)
)

Dabei bekommt die Standardkonfiguration eine eindeutige Revisionsnummer, und der Zeitstempel der Änderung wird gespeichert. Diese Daten lassen sich bei Bedarf mithilfe einer eigenen Revisionsentität erweitern, um zum Beispiel den Namen des Nutzers zu speichern, der die Änderung durchgeführt hat.

Die Tabellen zum Speichern der auditierten Daten folgen einem einfachen, konfigurierbaren Schema. Der Name der auditierten Tabelle wird um das Postfix _AUD erweitert, und die Tabelle speichert neben den auditierten Spalten der Originaltabelle einen Fremdschlüssel zur Revisionstabelle und die Art der Änderungen. Den Primärschlüssel bildet man aus dem der auditierten Tabelle und dem Fremdschlüssel der Revisionstabelle.

Das folgende Beispiel zeigt die Author-Entität, die in der gleichnamigen Tabelle mit den Spalten id, first name und last name gespeichert wird:

CREATE TABLE author_aud (
id
firstname character varying(255),
lastname character varying(255),
CONSTRAINT author_pkey PRIMARY KEY (id)
)

Für sie verwendet man somit die Tabelle Author_AUD mit den zusätzlichen Spalten rev und revtype zur Auditierung:

CREATE TABLE author_aud (
id bigint NOT NULL,
rev integer NOT NULL,
revtype smallint,
first name character varying(255),
last name character varying(255),
CONSTRAINT author_aud_pkey PRIMARY KEY (id, rev),
CONSTRAINT author_aud_revinfo FOREIGN KEY (rev)
REFERENCES revinfo (rev) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)

Nach diesem Schema ist nun für jede zu auditierende Tabelle eine Audittabelle anzulegen.

Nachdem die erforderlichen Tabellen angelegt wurden, sind die zu auditierenden Entitäten für Hibernate Envers zu identifizieren. Wenn alle Eigenschaften einer Entität untersucht werden sollen, hat man dazu lediglich die Klasse mit einer zusätzlichen @Audited-Annotation auszuzeichnen:

@Entity
@Audited
public class Author {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;

@Version
private int version;

@Column
private String firstName;

@Column
private String lastName;

@ManyToMany(mappedBy="authors")
private List<Book> books = new ArrayList<Book>();

...
}

Falls nur ein Teil der Eigenschaften auditiert werden soll, sind diese anstelle der Klasse mit einer @Audited-Annotation zu versehen.

Alle Änderungen, die Entwickler an einer mit @Audited gekennzeichneten Entität durchführen, werden mit den von Envers bereitgestellten Lifecycle-Event-Listenern automatisch auditiert. Dadurch ist sichergestellt, dass alle Operationen berücksichtigt werden und keine Anpassungen an bestehender Anwendungslogik notwendig sind, um die Auditierung zu einem Projekt hinzuzufügen::

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

Author a = new Author();
a.setFirstName("Thorben");
a.setLastName("Janssen");
em.persist(a);

em.getTransaction().commit();
em.close();

Für die folgenden Logausgaben hat der Autor den Loglevel für die Kategorie org.hibernate.SQL auf DEBUG gesetzt, damit Hibernate die ausgeführten SQL-Abfragen dokumentiert. Die Einstellung erleichtert das Verständnis der Datenbankinteraktionen und sollte zur Entwicklungskonfiguration für alle Anwendungen gehören, die Hibernate als OR-Mapper verwenden. Auch die Auditierung mit Hibernate Envers wird dadurch leichter verständlich:

10:15:42,727 DEBUG SQL:92 - 
select
nextval ('hibernate_sequence')
10:15:42,790 DEBUG SQL:92 -
insert
into
Author
(firstName, lastName, version, id)
values
(?, ?, ?, ?)
10:15:42,810 DEBUG SQL:92 -
select
nextval ('hibernate_sequence')
10:15:42,815 DEBUG SQL:92 -
insert
into
REVINFO
(REVTSTMP, REV)
values
(?, ?)
10:15:42,818 DEBUG SQL:92 -
insert
into
Author_AUD
(REVTYPE, firstName, lastName, id, REV)
values
(?, ?, ?, ?, ?)

Wie an den SQL Statements zu sehen ist, führt Hibernate erst eine Abfrage aus, um einen neuen Primärschlüsselwert für die Author-Entität zu erhalten, und persistiert diese anschließend in der Author-Tabelle. Die SQL-Kommandos wurden direkt durch den oben gezeigten Code ausgelöst.

Die nachfolgenden Statements hingegen führt Hibernate Envers automatisch aus, um die Persistierung einer neuen Author-Entität zu auditieren. Envers selektiert dazu zuerst einen neuen Primärschlüsselwert von der Datenbanksequenz hibernate_sequence und verwendet den Wert anschließend, um einen neuen Eintrag in der Revisionstabelle zu speichern. Die Revision wird mit allen innerhalb dieser Transaktion durchgeführten Änderungen verknüpft. Im nächsten Schritt persistiert Hibernate den neuen Author-Datensatz mit einer Fremdschlüsselreferenz auf die Revision und den Revisionstyp 0, der das Anlegen einer neuen Entität repräsentiert.

Wie das vorherige Beispiel gezeigt hat, sind nun die Voraussetzungen geschaffen, um alle Operationen an einer Author-Entität anzulegen, zu bearbeiten, zu löschen und im Auditlog zu dokumentieren. Für die meisten Anwendungen reicht das Schreiben eines Auditlogs aber nicht aus. Die Daten müssen auch ausgelesen und dem Anwender zur Verfügung gestellt werden können.