Auditing mit JPA, Hibernate und Spring Data JPA

Prüfung mit JPA, Hibernate und Spring Data JPA

1. Überblick

Im Kontext von ORM bedeutet Datenbankprüfung die Verfolgung und Protokollierung von Ereignissen in Bezug auf persistente Entitäten oder einfach die Versionierung von Entitäten. Inspiriert von SQL-Triggern handelt es sich bei den Ereignissen um Einfüge-, Aktualisierungs- und Löschvorgänge für Entitäten. Die Vorteile der Datenbanküberwachung entsprechen denen der Versionskontrolle.

Wir werden drei Ansätze zur Einführung der Prüfung in eine Anwendung demonstrieren. Zuerst werden wir es mit Standard-JPA implementieren. Als Nächstes werden wir uns zwei JPA-Erweiterungen ansehen, die ihre eigenen Überwachungsfunktionen bieten: eine von Hibernate, eine andere von Spring Data.

Hier sind die stichprobenbezogenen EntitätenBar undFoo,, die in diesem Beispiel verwendet werden:

image

2. Auditing mit JPA

JPA enthält keine explizite Überwachungs-API, die Funktionalität kann jedoch mithilfe von Entity-Lifecycle-Ereignissen erreicht werden.

2.1. @PrePersist,@PreUpdate und @ PreRemove

In der Klasse von JPAEntitykann eine Methode als Rückruf angegeben werden, der während eines bestimmten Entitätslebenszyklusereignisses aufgerufen wird. Da wir an Rückrufen interessiert sind, die vor den entsprechenden DML-Operationen ausgeführt werden, stehen für unsere Zwecke die Rückrufanmerkungen@PrePersist,@PreUpdate und@PreRemove zur Verfügung:

@Entity
public class Bar {

    @PrePersist
    public void onPrePersist() { ... }

    @PreUpdate
    public void onPreUpdate() { ... }

    @PreRemove
    public void onPreRemove() { ... }

}

Interne Rückrufmethoden sollten immervoid zurückgeben und keine Argumente annehmen. Sie können einen beliebigen Namen und eine beliebige Zugriffsebene haben, sollten jedoch nichtstatic sein.

Beachten Sie, dass die Annotation von@Versionin JPA nicht eng mit unserem Thema zusammenhängt - sie hat mehr mit optimistischem Sperren als mit Überwachungsdaten zu tun.

2.2. Rückrufmethoden implementieren

Es gibt jedoch eine signifikante Einschränkung bei diesem Ansatz. Wie in JPA2 specification (JSR 317): angegeben

Im Allgemeinen sollte die Lebenszyklusmethode einer tragbaren Anwendung nichtEntityManager oderQuery Operationen aufrufen, auf andere Entitätsinstanzen zugreifen oder Beziehungen innerhalb desselben Persistenzkontexts ändern. Eine Lifecycle-Callback-Methode kann den Nicht-Beziehungsstatus der Entität ändern, für die sie aufgerufen wird.

In Ermangelung eines Prüfungsrahmens müssen wir das Datenbankschema und das Domänenmodell manuell pflegen. In unserem einfachen Anwendungsfall fügen wir der Entität zwei neue Eigenschaften hinzu, da wir nur den "Nicht-Beziehungsstatus der Entität" verwalten können. Die Eigenschaftoperationpeichert den Namen einer ausgeführten Operation, und die Eigenschafttimestampgilt für den Zeitstempel der Operation:

@Entity
public class Bar {

    //...

    @Column(name = "operation")
    private String operation;

    @Column(name = "timestamp")
    private long timestamp;

    //...

    // standard setters and getters for the new properties

    //...

    @PrePersist
    public void onPrePersist() {
        audit("INSERT");
    }

    @PreUpdate
    public void onPreUpdate() {
        audit("UPDATE");
    }

    @PreRemove
    public void onPreRemove() {
        audit("DELETE");
    }

    private void audit(String operation) {
        setOperation(operation);
        setTimestamp((new Date()).getTime());
    }

}

Wenn Sie eine solche Überwachung mehreren Klassen hinzufügen müssen, können Sie@EntityListeners verwenden, um den Code zu zentralisieren. Zum Beispiel:

@EntityListeners(AuditListener.class)
@Entity
public class Bar { ... }
public class AuditListener {

    @PrePersist
    @PreUpdate
    @PreRemove
    private void beforeAnyOperation(Object object) { ... }

}

3. Envers im Ruhezustand

Mit Hibernate können wirInterceptors undEventListeners sowie Datenbank-Trigger verwenden, um die Prüfung durchzuführen. Das ORM-Framework bietet jedoch Envers, ein Modul, das die Überwachung und Versionierung persistenter Klassen implementiert.

3.1. Erste Schritte mit Envers

Um Envers einzurichten, müssen Sie die JARhibernate-enversin Ihren Klassenpfad einfügen:


    org.hibernate
    hibernate-envers
    ${hibernate.version}

Fügen Sie dann einfach die Annotation@Audited entweder zu@Entity (um die gesamte Entität zu prüfen) oder zu bestimmten@Columns hinzu (um nur bestimmte Eigenschaften zu prüfen):

@Entity
@Audited
public class Bar { ... }

Beachten Sie, dassBar eine Eins-zu-Viele-Beziehung zuFoo hat. In diesem Fall müssen wir entwederFoo ebenfalls prüfen, indem wir@Audited aufFoo addieren oder@NotAudited auf die Eigenschaft der Beziehung inBar setzen:

@OneToMany(mappedBy = "bar")
@NotAudited
private Set fooSet;

3.2. Erstellen von Überwachungsprotokolltabellen

Es gibt verschiedene Möglichkeiten, Audit-Tabellen zu erstellen:

  • Setzen Siehibernate.hbm2ddl.auto aufcreate,create-drop oderupdate, damit Envers sie automatisch erstellen kann

  • Verwenden Sie org.hibernate.tool.EnversSchemaGenerator, um das gesamte Datenbankschema programmgesteuert zu exportieren

  • Verwenden Sie eine Ant-Task, um entsprechende DDL-Anweisungen zu generieren

  • Verwenden Sie ein Maven-Plugin zum Generieren eines Datenbankschemas aus Ihren Zuordnungen (z. B. Juplo), um ein Envers-Schema zu exportieren (funktioniert mit Hibernate 4 und höher).

Wir werden den ersten Weg gehen, da dies der einfachste ist. Beachten Sie jedoch, dass die Verwendung vonhibernate.hbm2ddl.auto in der Produktion nicht sicher ist.

In unserem Fall solltenbar_AUD undfoo_AUD (wenn Sie auchFoo als@Audited festgelegt haben) automatisch generiert werden. Die Prüftabellen kopieren alle geprüften Felder aus der Entitätstabelle mit zwei Feldern,REVTYPE (Werte sind: "0" zum Hinzufügen, "1" zum Aktualisieren, "2" zum Entfernen einer Entität) undREV .

Außerdem wird standardmäßig eine zusätzliche Tabelle mit dem NamenREVINFO generiert, sie enthält zwei wichtige Felder,REV undREVTSTMP, und zeichnet den Zeitstempel jeder Revision auf. Und wie Sie sich vorstellen können, sindbar_AUD.REV undfoo_AUD.REV tatsächlich Fremdschlüssel fürREVINFO.REV.

3.3. Envers konfigurieren

Sie können die Envers-Eigenschaften wie jede andere Hibernate-Eigenschaft konfigurieren.

Ändern wir beispielsweise das Suffix der Prüftabelle (standardmäßig "AUD_“) to “AUDIT_LOG_"). So setzen Sie den Wert der entsprechenden Eigenschaftorg.hibernate.envers.audit_table_suffix:

Properties hibernateProperties = new Properties();
hibernateProperties.setProperty(
  "org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG");
sessionFactory.setHibernateProperties(hibernateProperties);

Eine vollständige Liste der verfügbaren Eigenschaften finden Sie inin the Envers documentation.

3.4. Zugriff auf den Entitätsverlauf

Sie können historische Daten auf ähnliche Weise wie Daten über die Ruhezustandskriterien-API abfragen. Auf den Prüfverlauf einer Entität kann über dieAuditReader-Schnittstelle zugegriffen werden, die mit einem offenenEntityManager oderSession über dieAuditReaderFactory abgerufen werden kann:

AuditReader reader = AuditReaderFactory.get(session);

Envers stelltAuditQueryCreator bereit (zurückgegeben vonAuditReader.createQuery()), um prüfungsspezifische Abfragen zu erstellen. In der folgenden Zeile werden alle Instanzen vonBarzurückgegeben, die bei Revision 2 geändert wurden (wobeibar_AUDIT_LOG.REV = 2):

AuditQuery query = reader.createQuery()
  .forEntitiesAtRevision(Bar.class, 2)

Hier erfahren Sie, wie Sie die Revisionen vonBarabfragen, d. H. Daraufhin wird eine Liste allerBar-Instanzen in allen geprüften Zuständen angezeigt:

AuditQuery query = reader.createQuery()
  .forRevisionsOfEntity(Bar.class, true, true);

Wenn der zweite Parameter false ist, wird das Ergebnis mit der TabelleREVINFOverknüpft. Andernfalls werden nur Entitätsinstanzen zurückgegeben. Der letzte Parameter gibt an, ob gelöschteBar-Instanzen zurückgegeben werden sollen.

Anschließend können Sie mithilfe der Factory-KlasseAuditEntityEinschränkungen angeben:

query.addOrder(AuditEntity.revisionNumber().desc());

4. Federdaten JPA

Spring Data JPA ist ein Framework, das JPA um eine zusätzliche Abstraktionsebene auf der Oberseite des JPA-Providers erweitert. Diese Ebene ermöglicht die Unterstützung beim Erstellen von JPA-Repositorys durch Erweiterung der Spring JPA-Repository-Schnittstellen.

Für unsere Zwecke können SieCrudRepository<T, ID extends Serializable> erweitern, die Schnittstelle für generische CRUD-Operationen. Sobald Sie Ihr Repository erstellt und einer anderen Komponente hinzugefügt haben, stellt Spring Data die Implementierung automatisch bereit und Sie können Überwachungsfunktionen hinzufügen.

4.1. Aktivieren der JPA-Überwachung

Zunächst möchten wir die Überwachung über die Anmerkungskonfiguration aktivieren. Fügen Sie dazu einfach@EnableJpaAuditing zu Ihrer@Configuration-Klasse hinzu:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig { ... }

4.2. Hinzufügen des Entity Callback Listener von Spring

Wie wir bereits wissen, stellt JPA die Annotation@EntityListenersbereit, um Callback-Listener-Klassen anzugeben. Spring Data bietet eine eigene JPA-Entity-Listener-Klasse:AuditingEntityListener. Geben wir also den Listener für die EntitätBaran:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar { ... }

Jetzt werden vom Listener Überwachungsinformationen erfasst, wenn die EntitätBarbeibehalten und aktualisiert wird.

4.3. Nachverfolgung der erstellten und zuletzt geänderten Daten

Als Nächstes fügen wir unserer EntitätBarzwei neue Eigenschaften zum Speichern der erstellten und zuletzt geänderten Daten hinzu. Die Eigenschaften werden entsprechend mit den Anmerkungen@CreatedDate und@LastModifiedDate versehen, und ihre Werte werden automatisch festgelegt:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {

    //...

    @Column(name = "created_date", nullable = false, updatable = false)
    @CreatedDate
    private long createdDate;

    @Column(name = "modified_date")
    @LastModifiedDate
    private long modifiedDate;

    //...

}

Im Allgemeinen verschieben Sie die Eigenschaften in eine Basisklasse (mit@MappedSuperClass versehen), die von allen geprüften Entitäten erweitert wird. In unserem Beispiel fügen wir sie der Einfachheit halber direkt zuBar hinzu.

4.4. Prüfung des Autors von Änderungen mit Spring Security

Wenn Ihre App Spring Security verwendet, können Sie nicht nur nachverfolgen, wann Änderungen vorgenommen wurden, sondern auch, wer sie vorgenommen hat:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {

    //...

    @Column(name = "created_by")
    @CreatedBy
    private String createdBy;

    @Column(name = "modified_by")
    @LastModifiedBy
    private String modifiedBy;

    //...

}

Die mit@CreatedBy und@LastModifiedBy beschrifteten Spalten werden mit dem Namen des Principals gefüllt, der die Entität erstellt oder zuletzt geändert hat. Die Informationen werden aus derSecurityContext-Instanz vonSecurityContextabgerufen. Wenn Sie Werte anpassen möchten, die auf die mit Anmerkungen versehenen Felder festgelegt sind, können Sie die Schnittstelle vonAuditorAware<T>implementieren:

public class AuditorAwareImpl implements AuditorAware {

    @Override
    public String getCurrentAuditor() {
        // your custom logic
    }

}

Um die App so zu konfigurieren, dassAuditorAwareImpl zum Nachschlagen des aktuellen Principals verwendet wird, deklarieren Sie eine Bean vom TypAuditorAware, die mit einer Instanz vonAuditorAwareImpl initialisiert wurde, und geben Sie den Namen der Bean alsauditorAwareRef parameter’s value in @EnableJpaAuditing:an ) s

@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {

    //...

    @Bean
    AuditorAware auditorProvider() {
        return new AuditorAwareImpl();
    }

    //...

}

5. Fazit

Wir haben drei Ansätze für die Implementierung von Überwachungsfunktionen in Betracht gezogen:

  • Der reine JPA-Ansatz ist der grundlegendste und besteht aus der Verwendung von Lifecycle-Callbacks. Sie dürfen jedoch nur den Nicht-Beziehungsstatus einer Entität ändern. Dies macht den Rückruf von@PreRemovefür unsere Zwecke unbrauchbar, da alle Einstellungen, die Sie in der Methode vorgenommen haben, zusammen mit der Entität gelöscht werden.

  • Envers ist ein ausgereiftes Überwachungsmodul von Hibernate. Es ist in hohem Maße konfigurierbar und weist die Mängel der reinen JPA-Implementierung auf. Auf diese Weise können wir den Löschvorgang überwachen, da er sich in anderen Tabellen als der Entitätstabelle anmeldet.

  • Der JPA-Ansatz von Spring Data abstrahiert die Arbeit mit JPA-Rückrufen und bietet praktische Anmerkungen für die Prüfung von Eigenschaften. Es kann auch in Spring Security integriert werden. Der Nachteil ist, dass der JPA-Ansatz dieselben Fehler aufweist, sodass der Löschvorgang nicht überprüft werden kann.

Die Beispiele für diesen Artikel sind ina GitHub repository verfügbar.