Dieser Text widmet sich verschiedenen Ansätzen zur Datenvalidierung: Auf welche Fallstricke kann ein Projekt stoßen und welche Methoden und Technologien sollten bei der Validierung von Daten in Java-Anwendungen angewendet werden.

Ich habe oft Projekte gesehen, deren Entwickler sich nicht die Mühe gemacht haben, einen Ansatz zur Datenvalidierung zu wählen. Die Teams arbeiteten unter unglaublichem Druck in Form von Fristen und vagen Anforderungen an dem Projekt und hatten daher einfach keine Zeit für eine genaue und konsistente Validierung. Daher ist ihr Validierungscode überall verstreut: in Javascript-Snippets, Bildschirmcontrollern, in Geschäftslogik-Bins, Domänenentitäten, Triggern und Datenbankeinschränkungen. Dieser Code war voll von if-else-Anweisungen, es gab eine Reihe von Ausnahmen und es wurde versucht herauszufinden, wo diese bestimmten Daten dort validiert sind. Infolgedessen wird es im Verlauf des Projekts schwierig und teuer, die Anforderungen zu erfüllen (oft recht verwirrend) Einheitlichkeit der Ansätze zur Datenvalidierung.
Gibt es also eine einfache und elegante Möglichkeit, Daten zu validieren? Gibt es einen Weg, der uns vor der Sünde der Unlesbarkeit schützt, der die gesamte Logik der Validierung zusammenfasst und der bereits von Entwicklern beliebter Java-Frameworks für uns entwickelt wurde?
Ja, so gibt es.
Für uns, die Entwickler der CUBA-Plattform , ist es sehr wichtig, dass Sie die Best Practices verwenden können. Wir glauben, dass der Validierungscode:
- Wiederverwendbar sein und dem DRY-Prinzip folgen;
- Sei natürlich und verständlich;
- Platziert dort, wo der Entwickler es erwartet;
- Sie können Daten aus verschiedenen Quellen überprüfen: Benutzeroberfläche, SOAP-Aufrufe, REST usw.
- Arbeiten Sie problemlos in einer Umgebung mit mehreren Threads.
- Wird innerhalb der Anwendung automatisch aufgerufen, ohne dass Überprüfungen manuell ausgeführt werden müssen.
- Um dem Benutzer klare, lokalisierte Nachrichten in übersichtlichen Dialogfeldern zu geben;
- Befolgen Sie die Standards.
Lassen Sie uns sehen, wie dies mithilfe einer Beispielanwendung implementiert werden kann, die mit dem CUBA Platform Framework geschrieben wurde. Da CUBA jedoch auf Spring und EclipseLink basiert, funktionieren die meisten der hier verwendeten Techniken auf jeder anderen Java-Plattform, die die JPA- und Bean-Validierungsspezifikationen unterstützt.
Validierung unter Verwendung von Datenbankeinschränkungen
Die wahrscheinlich häufigste und naheliegendste Methode zur Validierung von Daten besteht darin, Einschränkungen auf Datenbankebene zu verwenden, z. B. das erforderliche Flag (für Felder, deren Wert nicht leer sein darf), die Zeilenlänge, eindeutige Indizes usw. Diese Methode eignet sich am besten für Unternehmensanwendungen, da diese Art von Software normalerweise ausschließlich auf die Datenverarbeitung ausgerichtet ist. Selbst hier machen Entwickler jedoch häufig Fehler, indem sie die Grenzwerte für jede Ebene der Anwendung separat festlegen. Meistens liegt der Grund in der Verteilung der Verantwortlichkeiten zwischen den Entwicklern.
Stellen Sie sich ein Beispiel vor, das die meisten von uns kennen, einige sogar aus eigener Erfahrung ... Wenn die Spezifikation besagt, dass das Feld für die Passnummer 10 Zeichen enthalten sollte, wird dies höchstwahrscheinlich von allen überprüft: einem DB-Architekten in DDL, einem Backend-Entwickler in der entsprechenden Entität und REST-Services und schließlich der UI-Entwickler direkt auf der Client-Seite. Dann ändert sich diese Anforderung und das Feld wird auf 15 Zeichen erhöht. Devops ändern die Einschränkungswerte in der Datenbank, aber nichts ändert sich für den Benutzer, da auf der Clientseite die Einschränkung dieselbe ist ...
Jeder Entwickler weiß, wie er dieses Problem vermeiden kann - die Validierung muss zentralisiert werden! In CUBA befindet sich eine solche Validierung in JPA-Entitätsanmerkungen. Basierend auf diesen Metainformationen generiert CUBA Studio das richtige DDL-Skript und wendet die entsprechenden clientseitigen Validatoren an.

Wenn sich die Anmerkungen ändern, aktualisiert CUBA die DDL-Skripts und generiert die Migrationsskripts. Wenn Sie das Projekt das nächste Mal bereitstellen, werden neue JPA-basierte Einschränkungen sowohl in der Benutzeroberfläche als auch in der Anwendungsdatenbank wirksam.
Trotz der Einfachheit und Implementierung auf Datenbankebene, die dieser Methode absolute Zuverlässigkeit verleiht, ist der Umfang der JPA-Annotationen auf die einfachsten Fälle beschränkt, die im DDL-Standard ausgedrückt werden können und keine Datenbankauslöser oder gespeicherten Prozeduren enthalten. JPA-basierte Einschränkungen können daher ein Entitätsfeld eindeutig oder obligatorisch machen oder eine maximale Spaltenlänge festlegen. Mit der Annotation @UniqueConstraint
können Sie sogar eine eindeutige Einschränkung für die Kombination von Spalten @UniqueConstraint
. Aber das ist wahrscheinlich alles.
Wie auch immer, in Fällen, in denen eine komplexere Validierungslogik erforderlich ist, z. B. das Überprüfen eines Felds auf einen minimalen / maximalen Wert, das Validieren mit einem regulären Ausdruck oder das Durchführen einer benutzerdefinierten Prüfung, die nur für Ihre Anwendung spezifisch ist, wird der als "Bean Validation" bekannte Ansatz angewendet .
Bean-Validierung
Jeder weiß, dass es empfehlenswert ist, Standards mit einem langen Lebenszyklus zu befolgen, deren Wirksamkeit in Tausenden von Projekten nachgewiesen wurde. Java Bean Validation ist ein in JSR 380, 349 und 303 und ihren Anwendungen dokumentierter Ansatz: Hibernate Validator und Apache BVal .
Obwohl dieser Ansatz vielen Entwicklern bekannt ist, wird er häufig unterschätzt. Dies ist eine einfache Möglichkeit, die Datenvalidierung auch in ältere Projekte einzubetten, sodass Sie Validierungen klar, einfach, zuverlässig und so nah wie möglich an der Geschäftslogik erstellen können.
Die Verwendung der Bean-Validierung bietet dem Projekt viele Vorteile:
- Die Validierungslogik befindet sich neben dem Themenbereich: Die Definition der Einschränkungen für die Felder und Methoden des Bin erfolgt auf natürliche und wirklich objektorientierte Weise.
- Der Bean-Validierungsstandard bietet uns
@NotNull
Dutzende von Validierungsanmerkungen , zum Beispiel: @NotNull
, @Size
, @Min
, @Max
, @Pattern
, @Email
, @Past
, nicht ganz Standard @URL
, @Length
, das leistungsstärkste @ScriptAssert
und viele andere . - Der Standard beschränkt uns nicht auf vorgefertigte Anmerkungen und ermöglicht es uns, eigene zu erstellen. Wir können auch eine neue Annotation erstellen, indem wir mehrere andere kombinieren, oder sie mithilfe einer separaten Java-Klasse als Validator definieren.
Im obigen Beispiel können wir beispielsweise die @ValidPassportNumber
Klasse @ValidPassportNumber
, um zu überprüfen, ob die Passnummer dem Format entspricht, abhängig vom Wert des @ValidPassportNumber
. - Einschränkungen können nicht nur für Felder oder Klassen festgelegt werden, sondern auch für Methoden und deren Parameter. Dieser Ansatz wird als "vertragliche Validierung" bezeichnet und etwas später erörtert.
Wenn der Benutzer die eingegebenen Informationen übermittelt, startet die CUBA-Plattform (wie einige andere Frameworks) die Bean-Validierung automatisch, sodass sofort eine Fehlermeldung angezeigt wird, wenn die Validierung fehlschlägt und die Bin-Validatoren nicht manuell ausgeführt werden müssen.
Kehren wir zum Beispiel mit der Passnummer zurück, aber dieses Mal werden wir es durch einige Einschränkungen der Entität Person ergänzen:
- Das Namensfeld muss aus 2 oder mehr Zeichen bestehen und gültig sein. (Wie Sie sehen können, ist Regexp nicht einfach, aber "Charles Ogier de Batz de Castelmore Comte d'Artagnan" wird den Test bestehen, "R2D2" jedoch nicht);
height
(Höhe) sollte im folgenden Intervall liegen: 0 < height <= 300
cm;- Das
email
Feld muss eine Zeichenfolge enthalten, die dem Format der richtigen E-Mail entspricht.
Bei all diesen Überprüfungen sieht die Person-Klasse folgendermaßen aus:
@Listeners("passportnumber_PersonEntityListener") @NamePattern("%s|name") @Table(name = "PASSPORTNUMBER_PERSON") @Entity(name = "passportnumber$Person") @ValidPassportNumber(groups = {Default.class, UiCrossFieldChecks.class}) @FraudDetectionFlag public class Person extends StandardEntity { private static final long serialVersionUID = -9150857881422152651L; @Pattern(message = "Bad formed person name: ${validatedValue}", regexp = "^[AZ][az]*(\\s(([az]{1,3})|(([az]+\\')?[AZ][az]*)))*$") @Length(min = 2) @NotNull @Column(name = "NAME", nullable = false) protected String name; @Email(message = "Email address has invalid format: ${validatedValue}", regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$") @Column(name = "EMAIL", length = 120) protected String email; @DecimalMax(message = "Person height can not exceed 300 centimeters", value = "300") @DecimalMin(message = "Person height should be positive", value = "0", inclusive = false) @Column(name = "HEIGHT") protected BigDecimal height; @NotNull @Column(name = "COUNTRY", nullable = false) protected Integer country; @NotNull @Column(name = "PASSPORT_NUMBER", nullable = false, length = 15) protected String passportNumber; ... }
Person.java
Ich glaube, dass die Verwendung von Anmerkungen wie @NotNull
, @DecimalMin
, @Length
, @Pattern
und dergleichen ziemlich offensichtlich ist und keine Kommentare erfordert. Schauen wir uns die Implementierung der Annotation @ValidPassportNumber
genauer an.
Unsere brandneue @ValidPassportNumber
überprüft, ob Person#passportNumber
mit dem regulären Ausdrucksmuster für jedes Land übereinstimmt, das im Feld Person#country
ist.
Schauen wir uns zunächst die Dokumentation an (die CUBA- oder Hibernate- Handbücher sind in Ordnung). UiCrossFieldChecks.class
müssen wir unsere Klasse mit dieser neuen Annotation markieren und den UiCrossFieldChecks.class
an sie übergeben. UiCrossFieldChecks.class
bedeutet, dass diese Validierung am UiCrossFieldChecks.class
sollte Validierungen - Nachdem alle einzelnen Felder überprüft wurden und Default.class
die Einschränkung in der Standardvalidierungsgruppe speichert.
Die Beschreibung der Anmerkungen sieht folgendermaßen aus:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = ValidPassportNumberValidator.class) public @interface ValidPassportNumber { String message() default "Passport number is not valid"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
ValidPassportNumber.java
Hier gibt @Target(ElementType.TYPE)
, dass der Zweck dieser Laufzeitanmerkung die Klasse ist, und @Constraint(validatedBy = … )
bestimmt, dass die Validierung von der ValidPassportNumberValidator
Klasse durchgeführt wird, die die ConstraintValidator<...>
-Schnittstelle implementiert. Der Validierungscode selbst befindet sich in der Methode isValid(...)
, mit der die eigentliche Überprüfung auf recht einfache Weise durchgeführt wird:
public class ValidPassportNumberValidator implements ConstraintValidator<ValidPassportNumber, Person> { public void initialize(ValidPassportNumber constraint) { } public boolean isValid(Person person, ConstraintValidatorContext context) { if (person == null) return false; if (person.country == null || person.passportNumber == null) return false; return doPassportNumberFormatCheck(person.getCountry(), person.getPassportNumber()); } private boolean doPassportNumberFormatCheck(CountryCode country, String passportNumber) { ... } }
ValidPassportNumberValidator.java
Das ist alles. Mit der CUBA-Plattform müssen wir nur eine Codezeile schreiben, damit unsere benutzerdefinierte Validierung funktioniert und dem Benutzer Fehlermeldungen angezeigt werden.
Nichts kompliziertes, oder?
Nun wollen wir sehen, wie alles funktioniert. Hier hat CUBA andere Nishtyaki: Es zeigt dem Benutzer nicht nur eine Fehlermeldung an, sondern hebt auch in roten Feldern hervor, die die Bean-Validierung nicht bestanden haben:

Ist das nicht eine elegante Lösung? Sie erhalten eine angemessene Anzeige von Validierungsfehlern in der Benutzeroberfläche, indem Sie den Entitäten des Themenbereichs nur einige Java-Anmerkungen hinzufügen.
Um den Abschnitt zusammenzufassen, lassen Sie uns noch einmal kurz die Vorteile der Bean-Validierung für Entitäten auflisten:
- Es ist verständlich und lesbar;
- Ermöglicht das Definieren von Werteinschränkungen direkt in Entitätsklassen.
- Es kann angepasst und ergänzt werden;
- Integriert in gängige ORMs und Überprüfungen werden automatisch ausgeführt, bevor Änderungen in der Datenbank gespeichert werden.
- Einige Frameworks führen die Bean-Validierung auch automatisch aus, wenn der Benutzer Daten an die Benutzeroberfläche sendet (und wenn nicht, ist es einfach, die
Validator
Schnittstelle manuell aufzurufen). - Die Bean-Validierung ist ein anerkannter Standard und enthält zahlreiche Dokumentationen im Internet.
Was aber, wenn Sie eine Einschränkung für eine Methode, einen Konstruktor oder eine REST-Adresse festlegen müssen, um Daten zu validieren, die von einem externen System stammen? Oder wenn Sie die Werte der Methodenparameter deklarativ überprüfen müssen, ohne langweiligen Code mit vielen if-else-Bedingungen in jeder getesteten Methode zu schreiben?
Die Antwort ist einfach: Die Bean-Validierung gilt auch für Methoden!
Validierung durch Vertrag
Manchmal müssen Sie über die Validierung des Status des Datenmodells hinausgehen. Viele Methoden können von der automatischen Validierung von Parametern und Rückgabewerten profitieren. Dies kann nicht nur erforderlich sein, um die Daten zu überprüfen, die an REST- oder SOAP-Adressen gesendet werden, sondern auch in den Fällen, in denen die Vor- und Nachbedingungen von Methodenaufrufen notiert werden sollen, um sicherzustellen, dass die eingegebenen Daten vor der Ausführung des Methodenkörpers überprüft wurden oder der Rückgabewert liegt im erwarteten Bereich, oder wir müssen beispielsweise nur deklarativ die Wertebereiche der Eingabeparameter beschreiben, um die Lesbarkeit des Codes zu verbessern.
Mithilfe der Bean-Validierung können Einschränkungen auf die Eingabeparameter und Rückgabewerte von Methoden und Konstruktoren angewendet werden, um die Vor- und Nachbedingungen ihrer Aufrufe in einer beliebigen Java-Klasse zu überprüfen. Dieser Pfad bietet gegenüber herkömmlichen Methoden zur Überprüfung der Gültigkeit von Parametern und Rückgabewerten mehrere Vorteile:
- Es ist nicht erforderlich, Überprüfungen manuell in einem imperativen Stil durchzuführen (z. B. durch
IllegalArgumentException
von IllegalArgumentException
und dergleichen). Sie können Einschränkungen deklarativ definieren und den Code verständlicher und aussagekräftiger machen. - Einschränkungen können konfiguriert, wiederverwendet und konfiguriert werden: Sie müssen nicht für jede Prüfung eine Validierungslogik schreiben. Weniger Code bedeutet weniger Fehler.
- Wenn die Klasse, der Rückgabewert der Methode oder ihr Parameter mit der Annotation
@Validated
gekennzeichnet ist, werden die Überprüfungen bei jedem Aufruf der Methode automatisch von der Plattform durchgeführt. - Wenn die ausführbare Datei mit der Annotation
@Documented
gekennzeichnet ist, werden ihre Vor- und Nachbedingungen in das generierte JavaDoc aufgenommen.
Mit der 'Vertragsvalidierung' erhalten wir klaren, kompakten und einfach zu wartenden Code.
Schauen wir uns als Beispiel die Schnittstelle des REST-Controllers einer CUBA-Anwendung an. Über die PersonApiService
Schnittstelle können Sie mit der Methode getPersons()
eine Liste von Personen aus der Datenbank getPersons()
und mit dem addNewPerson(...)
eine neue Person hinzufügen.
Und vergessen Sie nicht, dass die Bean-Validierung vererbt wird! Mit anderen Worten, wenn wir eine bestimmte Klasse, ein bestimmtes Feld oder eine bestimmte Methode mit Anmerkungen versehen, unterliegen alle Klassen, die diese Klasse erben oder diese Schnittstelle implementieren, derselben Validierungsanmerkung.
@Validated public interface PersonApiService { String NAME = "passportnumber_PersonApiService"; @NotNull @Valid @RequiredView("_local") List<Person> getPersons(); void addNewPerson( @NotNull @Length(min = 2, max = 255) @Pattern(message = "Bad formed person name: ${validatedValue}", regexp = "^[AZ][az]*(\\s(([az]{1,3})|(([az]+\\')?[AZ][az]*)))*$") String name, @DecimalMax(message = "Person height can not exceed 300 cm", value = "300") @DecimalMin(message = "Person height should be positive", value = "0", inclusive = false) BigDecimal height, @NotNull CountryCode country, @NotNull String passportNumber ); }
PersonApiService.java
Ist dieser Code klar genug?
_ (Mit Ausnahme der Annotation @RequiredView(“_local”)
, die für die CUBA-Plattform spezifisch ist und überprüft, ob das zurückgegebene Person
Objekt alle Felder aus der Tabelle PASSPORTNUMBER_PERSON enthält) ._
Die @Valid
definiert, dass jedes von der Methode getPersons()
zurückgegebene getPersons()
auch gegen die Einschränkungen der Person
Klasse validiert werden muss.
In einer CUBA-Anwendung stehen diese Methoden unter folgenden Adressen zur Verfügung:
- / app / rest / v2 / services / passportnumber_PersonApiService / getPersons
- / app / rest / v2 / services / passportnumber_PersonApiService / addNewPerson
Öffnen Sie die Postman-Anwendung und stellen Sie sicher, dass die Validierung ordnungsgemäß funktioniert:

Wie Sie vielleicht bemerkt haben, wird die Passnummer im obigen Beispiel nicht validiert. Dies liegt daran, dass für dieses Feld eine Überprüfung der Parameter der addNewPerson
Methode addNewPerson
ist, da die Auswahl einer Vorlage für reguläre Ausdrücke zur Überprüfung von passportNumber
vom Wert des addNewPerson
abhängt. Diese Kreuzvalidierung ist ein vollständiges Analogon zu Entitätsbeschränkungen auf Klassenebene!
Die Kreuzvalidierung von Parametern wird von JSR 349 und 380 unterstützt. In der Dokumentation zum Ruhezustand erfahren Sie, wie Sie Ihre eigene Kreuzvalidierung von Klassen- / Schnittstellenmethoden implementieren.
Validierung außerhalb der Bohnen
Es gibt keine Perfektion auf der Welt, daher hat die Bohnenvalidierung ihre Nachteile und Grenzen:
- Manchmal müssen wir nur den Status eines komplexen Diagramms von Objekten überprüfen, bevor wir Änderungen an der Datenbank speichern. Beispielsweise müssen Sie sicherstellen, dass alle Elemente einer Kundenbestellung in einem Paket enthalten sind. Dies ist ein ziemlich schwieriger Vorgang, und es ist keine gute Idee, ihn jedes Mal auszuführen, wenn der Benutzer der Bestellung neue Artikel hinzufügt. Daher ist eine solche Prüfung möglicherweise nur einmal erforderlich: bevor das
Order
Objekt und seine OrderItem
Unterobjekte in der Datenbank gespeichert werden. - Einige Überprüfungen müssen innerhalb einer Transaktion durchgeführt werden. Beispielsweise sollte das elektronische Speichersystem prüfen, ob genügend Kopien der Waren vorhanden sind, um die Bestellung zu erfüllen, bevor sie in die Datenbank übernommen werden. Eine solche Prüfung kann nur innerhalb einer Transaktion durchgeführt werden, weil Das System ist multithreaded und die Menge der auf Lager befindlichen Waren kann sich jederzeit ändern.
Die CUBA-Plattform bietet zwei Datenüberprüfungsmechanismen vor dem Festschreiben, die als Entity-Listener und Transaktions-Listener bezeichnet werden . Betrachten wir sie genauer.
Entity Listemers
Entity-Listener in CUBA sind den PreInsertEvent
, PreUpdateEvent
und PredDeleteEvent
Listenern , die JPA dem Entwickler anbietet, sehr ähnlich. Mit beiden Mechanismen können Sie Entitätsobjekte überprüfen, bevor und nachdem sie in der Datenbank gespeichert wurden.
In CUBA ist es einfach, einen Entity-Listener zu erstellen und zu verbinden. Dazu benötigen Sie zwei Dinge:
- Erstellen Sie eine verwaltete Bean, die eine der Entity-Listener-Schnittstellen implementiert. 3 Schnittstellen sind wichtig für die Validierung:
BeforeDeleteEntityListener<T>
,
BeforeInsertEntityListener<T>
,
BeforeUpdateEntityListener<T>
- Fügen Sie die Annotation
@Listeners
zu dem Entitätsobjekt hinzu, das Sie verfolgen @Listeners
.
Und alle.
Im Vergleich zum JPA-Standard (JSR 338, Abschnitt 3.5) sind die CUBA Platform-Listener-Schnittstellen typisiert, sodass Sie kein Argument vom Typ Object
in einen Entitätstyp umwandeln müssen, um damit arbeiten zu können. Die CUBA-Plattform fügt verwandten Entitäten oder EntityManager-Aufrufern die Möglichkeit hinzu, andere Entitäten zu laden und zu ändern. Alle diese Änderungen rufen auch den entsprechenden Entity-Listener auf.
Die CUBA-Plattform unterstützt auch das "weiche Löschen" , ein Ansatz, bei dem Datensätze nicht tatsächlich aus der Datenbank gelöscht, sondern nur als gelöscht markiert werden und für den normalen Gebrauch nicht mehr zugänglich sind. Zum sanften Löschen ruft die Plattform die BeforeDeleteEntityListener
/ AfterDeleteEntityListener
, während Standardimplementierungen PreUpdate
/ PostUpdate
aufrufen PreUpdate
.
Schauen wir uns ein Beispiel an. Hier stellt die Ereignis-Listener-Bean mit nur einer Codezeile eine Verbindung zur Entitätsklasse her: der Annotation @Listeners
, die den Namen der Listener-Klasse trägt:
@Listeners("passportnumber_PersonEntityListener") @NamePattern("%s|name") @Table(name = "PASSPORTNUMBER_PERSON") @Entity(name = "passportnumber$Person") @ValidPassportNumber(groups = {Default.class, UiCrossFieldChecks.class}) @FraudDetectionFlag public class Person extends StandardEntity { ... }
Person.java
Die Listener-Implementierung selbst sieht folgendermaßen aus:
@Component("passportnumber_PersonEntityListener") public class PersonEntityListener implements BeforeDeleteEntityListener<Person>, BeforeInsertEntityListener<Person>, BeforeUpdateEntityListener<Person> { @Override public void onBeforeDelete(Person person, EntityManager entityManager) { if (!checkPassportIsUnique(person.getPassportNumber(), person.getCountry(), entityManager)) { throw new ValidationException( "Passport and country code combination isn't unique"); } } @Override public void onBeforeInsert(Person person, EntityManager entityManager) {
PersonEntityListener.java
Entity Listener sind eine gute Wahl, wenn:
- Es ist erforderlich, die Daten innerhalb der Transaktion zu überprüfen, bevor das Entitätsobjekt in der Datenbank gespeichert wird.
- Es ist erforderlich, die Daten in der Datenbank während des Validierungsprozesses zu überprüfen, um beispielsweise zu überprüfen, ob genügend Produkte auf Lager sind, um die Bestellung anzunehmen.
- Sie müssen nicht nur das Entitätsobjekt wie
Order
, sondern auch verwandte Entitäten, z. B. OrderItems
für die Order
Entität. - Wir möchten die Einfüge-, Aktualisierungs- oder Löschvorgänge nur für bestimmte Entitätsklassen
OrderItem
, z. B. nur für OrderItem
und OrderItem
, und müssen während der Transaktion nicht nach Änderungen in anderen Entitätsklassen OrderItem
.
Transaktions-Listener
CUBA-Transaktions-Listener agieren auch im Kontext von Transaktionen, werden jedoch im Vergleich zu Entity-Listenern für jede Datenbanktransaktion aufgerufen.
Diese geben ihnen Superkraft:
- nichts kann ihrer Aufmerksamkeit entgehen.
Dies wird jedoch durch ihre Mängel bestimmt:
- sie sind schwieriger zu schreiben;
- Sie können die Leistung erheblich reduzieren.
- Sie sollten sehr sorgfältig geschrieben werden: Ein Fehler im Transaktions-Listener kann sogar das anfängliche Laden der Anwendung beeinträchtigen.
Transaktions-Listener sind daher eine gute Lösung, wenn Sie verschiedene Entitätstypen mit demselben Algorithmus untersuchen müssen, z. B. indem Sie alle Daten mit einem einzigen Dienst auf Cyber-Betrug überprüfen, der alle Ihre Geschäftsobjekte bedient.

Schauen Sie sich ein Beispiel an, das prüft, ob die Entität über eine @FraudDetectionFlag
Annotation verfügt, und startet gegebenenfalls einen Betrugsdetektor. Ich wiederhole: Denken Sie daran, dass diese Methode vor dem Festschreiben jeder Datenbanktransaktion auf dem System aufgerufen wird. Daher sollte der Code versuchen, so wenige Objekte wie möglich zu überprüfen.
@Component("passportnumber_ApplicationTransactionListener") public class ApplicationTransactionListener implements BeforeCommitTransactionListener { private Logger log = LoggerFactory.getLogger(ApplicationTransactionListener.class); @Override public void beforeCommit(EntityManager entityManager, Collection<Entity> managedEntities) { for (Entity entity : managedEntities) { if (entity instanceof StandardEntity && !((StandardEntity) entity).isDeleted() && entity.getClass().isAnnotationPresent(FraudDetectionFlag.class) && !fraudDetectorFeedAndFastCheck(entity)) { logFraudDetectionFailure(log, entity); String msg = String.format( "Fraud detection failure in '%s' with id = '%s'", entity.getClass().getSimpleName(), entity.getId()); throw new ValidationException(msg); } } } ... }
ApplicationTransactionListener.java
Um ein Transaktionslistener zu werden, muss eine verwaltete Bean die BeforeCommitTransactionListener
Schnittstelle und die beforeCommit
Methode implementieren. Transaktionslistener binden automatisch, wenn die Anwendung gestartet wird. CUBA registriert alle Klassen, die BeforeCommitTransactionListener
oder AfterCompleteTransactionListener
als Transaktionslistener.
Fazit
Die Bean-Validierung (JPA 303, 349 und 980) ist ein Ansatz, der als zuverlässige Grundlage für 95% der Datenvalidierungsfälle in einem Unternehmensprojekt dienen kann. Der Hauptvorteil dieses Ansatzes besteht darin, dass der größte Teil der Validierungslogik direkt in den Domänenmodellklassen konzentriert ist. Daher ist es leicht zu finden, leicht zu lesen und leicht zu warten. Spring, CUBA und viele andere Bibliotheken unterstützen diese Standards und führen automatisch Validierungsprüfungen durch, wenn Daten auf der UI-Ebene empfangen, validierte Methoden aufgerufen oder Daten über ORM gespeichert werden. Daher sieht die Bean-Validierung aus Entwicklersicht oft magisch aus.
Einige Softwareentwickler betrachten die Validierung auf Klassenebene des Subjektmodells als unnatürlich und zu kompliziert. Sie sagen, dass die Datenvalidierung auf UI-Ebene eine ziemlich effektive Strategie ist. Ich glaube jedoch, dass zahlreiche Validierungspunkte in Komponenten und UI-Controllern nicht der rationalste Ansatz sind. , , , , , listener' .
, , :
- JPA , , DDL.
- Bean Validation — , , , . , .
- bean validation, . , , REST.
- Entity listeners: , Bean Validation, . , . Hibernate .
- Transaction listeners — , , . , , .
PS: , Java, , , .
Nützliche Links
Versteckter TextStandards und deren Umsetzung
Bibliotheken