Java >> Java Tutorial >  >> Tag >> Spring

Spring Data JPA-Lernprogramm:Prüfung, Teil Zwei

Wenn wir das Wort Audit hören, fällt uns als Erstes ein Audit-Protokoll ein, das jede Version der geprüften Entität enthält. Die Implementierung eines Audit-Logs ist eine komplexe Aufgabe, die viel Zeit in Anspruch nimmt. Glücklicherweise müssen wir dies meistens nicht tun.

Es kommt jedoch häufig vor, dass wir in der Lage sein müssen, die folgenden Fragen zu beantworten:

  • Wann wurde die Entität X erstellt und/oder geändert?
  • Wer hat die Entität X erstellt und/oder modifiziert?
Im vorherigen Teil dieses Tutorials wurde beschrieben, wie wir die Überwachungsinfrastruktur von Spring Data JPA verwenden können, um die Antwort auf die erste Frage zu finden.

Dieser Blogbeitrag beschreibt, wie wir die Antwort auf die zweite Frage finden können. Wir ändern unsere Beispielanwendung, um den Benutzernamen des authentifizierten Benutzers zu speichern, der einen neuen Aufgabeneintrag erstellt und die Informationen eines vorhandenen Aufgabeneintrags aktualisiert hat.

Beginnen wir mit der Erstellung einer Komponente, die die Informationen des authentifizierten Benutzers zurückgibt.

Abrufen der Informationen des authentifizierten Benutzers

Die Überwachungsinfrastruktur von Spring Data JPA verwendet AuditorAware Schnittstelle, wenn sie die Informationen des authentifizierten Benutzers abrufen muss. Das AuditorAware Schnittstelle hat einen Typparameter (T ), der den Typ des Felds der Entität beschreibt, das die Prüfinformationen enthält.

Da wir eine Klasse erstellen müssen, die den Benutzernamen des authentifizierten Benutzers zurückgibt, müssen wir diesen Schritten folgen:

  1. Erstellen Sie BenutzernameAuditorAware Klasse und implementieren Sie AuditorAware Schnittstelle. Da wir den Benutzernamen des authentifizierten Benutzers speichern möchten (String ), müssen wir den Wert des Typparameters auf String setzen .
  2. Implementieren Sie getCurrentAuditor() Methode, indem Sie die folgenden Schritte ausführen:
    1. Holen Sie sich eine Authentifizierung Objekt aus dem SecurityContext .
    2. Gib null zurück wenn die Authentifizierung nicht gefunden wird oder die gefundene Authentifizierung nicht authentifiziert ist.
    3. Gibt den Benutzernamen des authentifizierten Benutzers zurück.

Der Quellcode von UsernameAuditorAware Klasse sieht wie folgt aus:

import org.springframework.data.domain.AuditorAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;

public class UsernameAuditorAware implements AuditorAware<String> {

    @Override
    public String getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null || !authentication.isAuthenticated()) {
            return null;
        }

        return ((User) authentication.getPrincipal()).getUsername();
    }
}

Lassen Sie uns weitermachen und herausfinden, wie wir den Anwendungskontext unserer Beispielanwendung konfigurieren können.

Konfigurieren des Anwendungskontexts

Wir können den Anwendungskontext unserer Anwendung konfigurieren, indem wir die folgenden Änderungen an der Konfigurationsklasse vornehmen, die die Persistenzschicht unserer Anwendung konfiguriert:

  1. Erstellen Sie einen auditorProvider() -Methode, die einen AuditorAware zurückgibt Objekt.
  2. Implementieren Sie die Methode, indem Sie einen neuen UsernameAuditorAware erstellen Objekt.
  3. Kommentieren Sie die Methode mit @Bean Anmerkung.
  4. Aktivieren Sie die Auditing-Unterstützung von Spring Data JPA, indem Sie die Konfigurationsklasse mit @EnableJpaAuditing kommentieren Anmerkung.

Der relevante Teil des PersistenceContext Klasse sieht wie folgt aus:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 
@Configuration
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
@EnableJpaRepositories(basePackages = {
        "net.petrikainulainen.springdata.jpa.todo"
})
@EnableTransactionManagement
class PersistenceContext {

	@Bean
	AuditorAware<String> auditorProvider() {
		return new UsernameAuditorAware();
	}
 
    @Bean
    DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
        return new AuditingDateTimeProvider(dateTimeService);
    }
}

Weil wir nur ein AuditorAware deklariert haben Bean findet die Prüfinfrastruktur sie automatisch und verwendet sie, wenn sie die Informationen des authentifizierten Benutzers in das/die Feld(er) des gespeicherten oder aktualisierten Entitätsobjekts setzen muss. Wenn wir mehrere AuditorAware deklarieren Beans können wir die verwendete Bean konfigurieren, indem wir den Wert von auditorAwareRef setzen Attribut von @EnableJpaAuditing Anmerkung.

Fahren wir fort und nehmen die erforderlichen Änderungen an unserer Entitätsklasse vor.

Ändern unserer Entitätsklasse

Wir müssen die folgenden Änderungen an unserer Entitätsklasse vornehmen (Todo ):

  1. Wir müssen sicherstellen, dass der Wert von createdByUser Das Feld wird gesetzt, wenn unsere Entität zum ersten Mal persistiert wird.
  2. Wir müssen sicherstellen, dass der Wert von modifiedByUser Das Feld wird gesetzt, wenn unsere Entität zum ersten Mal beibehalten wird, und aktualisiert, wenn die Informationen unserer Entität aktualisiert werden.

Wir können diese Änderungen vornehmen, indem wir diesen Schritten folgen:

  1. Fügen Sie einen createdByUser hinzu Feld in die Entitätsklasse, setzen Sie seinen Typ auf String , und befolgen Sie diese Schritte:
    1. Kommentieren Sie das Feld mit @Column Anmerkung. Konfigurieren Sie den Namen der Datenbankspalte (created_by_user ) und stellen Sie sicher, dass der Wert dieser Spalte nicht null sein darf .
    2. Kommentieren Sie das Feld mit @CreatedBy Anmerkung. Dies identifiziert das Feld, das die Informationen des Benutzers enthält, der die Entität erstellt hat.
  2. Fügen Sie einen modifiedByUser hinzu Feld in die Entitätsklasse, setzen Sie seinen Typ auf String , und befolgen Sie diese Schritte
    1. Kommentieren Sie das Feld mit @Column Anmerkung. Konfigurieren Sie den Namen der Datenbankspalte (modified_by_user ) und stellen Sie sicher, dass der Wert dieser Spalte nicht null sein darf .
    2. Kommentieren Sie das Feld mit @LastModifiedBy Anmerkung. Dies identifiziert das Feld, das die Informationen des Benutzers enthält, der die letzten Änderungen an der Entität vorgenommen hat.

Der relevante Teil der Todo Klasse sieht wie folgt aus:

import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import java.time.ZonedDateTime;

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "todos")
final class Todo {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "created_by_user", nullable = false)
    @CreatedBy
    private String createdByUser;

    @Column(name = "creation_time", nullable = false)
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    @CreatedDate
    private ZonedDateTime creationTime;

    @Column(name = "description", length = 500)
    private String description;

    @Column(name = "modified_by_user", nullable = false)
    @LastModifiedBy
    private String modifiedByUser;

    @Column(name = "modification_time")
    @Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
    @LastModifiedDate
    private ZonedDateTime modificationTime;

    @Column(name = "title", nullable = false, length = 100)
    private String title;

    @Version
    private long version;
}

Normalerweise ist es eine gute Idee, die Audit-Felder einer abstrakten Basisklasse hinzuzufügen. Der Grund, warum ich es hier nicht getan habe, ist, dass unsere Beispielanwendung nur eine Entität hat und ich die Dinge so einfach wie möglich halten wollte.

Wenn wir diese Informationen in die abstrakte Basisklasse verschieben würden, würde ihr Quellcode wie folgt aussehen:

import org.hibernate.annotations.Type;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.MappedSuperClass

@EntityListeners(AuditingEntityListener.class)
@MappedSuperClass
public abstract class BaseEntity {
 
	@Column(name = "created_by_user", nullable = false)
	@CreatedBy
	private String createdByUser;
	
	@Column(name = "creation_time", nullable = false)
	@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
	@CreatedDate
	private ZonedDateTime creationTime; 
 
	@Column(name = "modified_by_user", nullable = false)
	@LastModifiedBy
	private String modifiedByUser;
	
	@Column(name = "modification_time")
	@Type(type = "org.jadira.usertype.dateandtime.threeten.PersistentZonedDateTime")
	@LastModifiedDate
	private ZonedDateTime modificationTime;
}

Lassen Sie uns herausfinden, warum wir die Auditing-Unterstützung von Spring Data JPA anstelle der in der Java Persistence API angegebenen Callback-Methoden verwenden sollten.

Warum sollten wir die Prüfungsinfrastruktur von Spring Data JPA verwenden?

Wenn wir die Informationen des Benutzers speichern möchten, der unsere Entität erstellt und aktualisiert hat, müssen wir Spring Data JPA nicht verwenden. Wir können die Werte dieser Felder festlegen (createdByUser und modifiedByUser ) durch Erstellen von Callback-Methoden, die an die Lebenszyklusereignisse der Entität angehängt werden.

Der Quellcode einer abstrakten Basisklasse, die diese Methode verwendet, sieht wie folgt aus:

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.LastModifiedBy;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
 
import javax.persistence.Column;
import javax.persistence.MappedSuperClass
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
 
@MappedSuperClass
public abstract class BaseEntity {
 
	@Column(name = "created_by_user", nullable = false)
	@CreatedBy
	private String createdByUser; 
	
	@Column(name = "modified_by_user", nullable = false)
	@LastModifiedBy
	private String modifiedByUser;

	
    @PrePersist
    public void prePersist() {
		String createdByUser = getUsernameOfAuthenticatedUser();
		this.createdByUser = createdByUser;
		this.modifiedByUser = createdByUser;
    }
     
    @PreUpdate
    public void preUpdate() {
		String modifiedByUser = getUsernameOfAuthenticatedUser();
		this.modifiedByUser = modifiedByUser;
    }
	
	private String getUsernameOfAuthenticatedUser() {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

		if (authentication == null || !authentication.isAuthenticated()) {
			return null;
		}

		return ((User) authentication.getPrincipal()).getUsername();
	}
}

Auch wenn diese Methode etwas einfacher und unkomplizierter ist als die Verwendung der Überwachungsinfrastruktur von Spring Data JPA, gibt es zwei Gründe, warum wir eine komplexere Lösung in Betracht ziehen sollten:

Zuerst , erstellt die Verwendung von Callback-Methoden eine Kopplung zwischen unserer Basisklasse (oder Entitätsklassen) und Spring Security, und ich möchte dies vermeiden.

Zweite , wenn wir die Werte der Erstellungs- und Änderungszeitfelder festlegen müssen UND uns entschieden haben, die Überwachungsinfrastruktur von Spring Data JPA für diesen Zweck zu verwenden, sollten wir sie zum Festlegen der Feldwerte von createdByUser verwenden und modifiedByUser Felder, weil es keinen Sinn macht, die Audit-Informationen mit zwei verschiedenen Mechanismen festzulegen.

Fassen wir zusammen, was wir aus diesem Blogbeitrag gelernt haben.

Zusammenfassung

Dieser Blogbeitrag hat uns drei Dinge gelehrt:

  • Der AuditorAware interface deklariert die Methode, die die Informationen des authentifizierten Benutzers für die Überwachungsinfrastruktur von Spring Data JPA bereitstellt.
  • Wir können die Audit-Felder mithilfe von Anmerkungen identifizieren und die Werte durch Implementieren von Auditable festlegen Schnittstelle oder erweitern Sie das AbstractAuditable Klasse.
  • Es ist einfacher, die Werte der Audit-Felder mithilfe von Callback-Methoden festzulegen, die an die Lebenszyklusereignisse der Entität angehängt sind. Der Nachteil dieser Methode ist, dass sie eine Kopplung zwischen unserer abstrakten Basisklasse (oder Entitätsklassen) und Spring Security herstellt.

Der nächste Teil dieses Tutorials beschreibt, wie wir benutzerdefinierte Methoden zu einem einzigen Repository hinzufügen können.

P.S. Sie können die Beispielanwendung dieses Blogbeitrags von Github herunterladen.


Java-Tag