Bean Validation in JPA
Die Datenvalidierung, einschließlich Persistenz, ist eine allgemeine Task, die in allen Schichten einer Anwendung ausgeführt wird. Java™ Persistence API (JPA) unterstützt die Bean Validation API, damit die Datenvalidierung zur Laufzeit durchgeführt werden kann. Dieser Artikel enthält ein Einsatzszenario, in dem Bean Validation in der JPA-Umgebung einer Beispielanwendung für eine digitale Bildsammlung verwendet wird.
Die Bean Validation API ermöglicht in Umgebungen mit Java Enterprise Edition (Java EE) und Java Platform, Standard Edition (JSE) eine technologieübergreifende nahtlose Validierung. Diese Technologien umfassen zusätzlich zu JPA auch JavaServer Faces (JSF) und Java EE Connector Architecture (JCA). Weitere Informationen zu Bean Validation finden Sie im Artikel "Bean Validation API".
Es gibt drei Bean-Validation-Basiskonzepte: Integritätsbedingungen, Behandlung von Verstößen gegen Integritätsbedingungen (ungültige Integritätsbedingungen) und Validator. Wenn Sie Anwendungen in einer integrierten Umgebung wie JPA ausführen, ist keine direkte Schnittschnelle zum Validator erforderlich.
Integritätsbedingungen für Bean Validation sind Annotationen oder XML-Code, der einer Klasse, einem Feld oder einer Methode einer JavaBeans-Komponente hinzugefügt wird. Integritätsbedingungen können integriert oder benutzerdefiniert sein. Sie werden verwendet, um reguläre Integritätsbedingungsdefinitionen zu definieren und um Integritätsbedingungen zu erstellen. Die integrierten Integritätsbedingungen werden von der Spezifikation Bean Validation definiert und sind für jeden Validation-Provider verfügbar. Eine Liste der integrierten Integritätsbedingungen finden Sie im Artikel "Integrierte Integritätsbedingungen für Bean Validation". Wenn Sie andere Integritätsbedingungen benötigen als die integrierten, können Sie eigene Integritätsbedingungen erstellen.
Integritätsbedingungen und JPA
Das folgende Einsatzszenario veranschaulicht, wie eine integrierte Integritätsbedingungen in der JPA-Architektur einer Beispiel für eine digitale Bildsammlung verwendet wird.
package org.apache.openjpa.example.gallery.model;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@Entity
public class Image {
private long id;
private ImageType type;
private String fileName;
private byte[] data;
@Id
@GeneratedValue
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@NotNull(message="Image type must be specified.")
@Enumerated(EnumType.STRING)
public ImageType getType() {
return type;
}
public void setType(ImageType type) {
this.type = type;
}
@Pattern(regexp = ".*\\.jpg|.*\\.jpeg|.*\\.gif",
message="Only images of type JPEG or GIF are supported.")
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
Die Klasse "Image" verwendet zwei integrierte Integritätsbedingungen, @NotNull und @Pattern. Mit der Integritätsbedingung @NotNull wird sichergestellt, dass ein Element des Typs "ImageType" (Bildtyp) angegeben wird, und die Integritätsbedingung @Pattern verwendet eine Mustererkennung für reguläre Ausdrücke, um sicherzustellen, dass der Name der Bilddatei eine Erweiterung verwendet, die mit einem unterstützten Bildformat übereinstimmt. Jede Integritätsbedingung verfügt über eine zugehörige Validierungslogik, die zur Ausführungszeit gestartet wird, wenn die Entität "Image" (Bild) ausgewertet wird. Wird eine der Integritätsbedingungen nicht erfüllt, löst der JPA-Provider eine Ausnahme des Typs "ConstraintViolationException" mit einer definierten Nachricht aus. Die Spezifikation JSR-303 unterstützt auch die Verwendung von Variablen im Nachrichtenattribut. Die Variable referenziert eine Nachricht mit Schlüssel in einem Ressourcenpaket. Das Ressourcenpaket unterstützt umgebungsspezifische Nachrichten sowie die Globalisierung, Übersetzung und Eignung für kulturübergreifenden Einsatz von Nachrichten.
package org.apache.openjpa.example.gallery.constraint;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import javax.validation.Constraint;
import javax.validation.Payload;
import org.apache.openjpa.example.gallery.model.ImageType;
@Documented
@Constraint(validatedBy = ImageContentValidator.class)
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface ImageContent {
String message() default "Image data is not a supported format.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
ImageType[] value() default { ImageType.GIF, ImageType.JPEG };
}
Als Nächstes müssen Sie die Validatorklasse "ImageContentValidator" erstellen.
Die Logik in diesem Validator wird vom Valdiation-Provider
implementiert, wenn die Integritätsbedingung validiert wird.
Die Validatorklasse wird über das Attribut "validatedBy" in der Annotation "@Constraint"
an die Integritätsbedingungsannotation gebunden, wie im folgenden Beispiel gezeigt wird:
package org.apache.openjpa.example.gallery.constraint;
import java.util.Arrays;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.openjpa.example.gallery.model.ImageType;
/**
* Einfache Prüfung, ob das Dateiformat unterstützt wird
*/
public class ImageContentValidator implements ConstraintValidator<ImageContent, byte[]> {
private List<ImageType> allowedTypes = null;
/**
* Integritätsbedingungsvalidator basierend auf den zu unterstützenden Bildtypen
* konfigurieren. * @param constraint Die Definition der Integritätsbedingungsdefinition.
*/
public void initialize(ImageContent constraint) {
allowedTypes = Arrays.asList(constraint.value());
}
/**
* Angegebenen Wert validieren. */
public boolean isValid(byte[] value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
// Prüfen, ob der GIF-Header GIF87 oder GIF89 ist.
if (allowedTypes.contains(ImageType.GIF)) {
String gifHeader = new String(value, 0, 6);
if (value.length >= 6 &&
(gifHeader.equalsIgnoreCase("GIF87a") ||
gifHeader.equalsIgnoreCase("GIF89a"))) {
return true;
}
}
// Überprüfen, ob JPEG mit SOI beginnt und auf EOI endet
if (allowedTypes.contains(ImageType.JPEG)) {
if (value.length >= 4 &&
value[0] == 0xff && value[1] == 0xd8 &&
value[value.length - 2] == 0xff && value[value.length -1] == 0xd9) {
return true;
}
}
// Unbekanntes Dateiformat
return false;
}
}
Wenden Sie diese neue Integritätsbedingung auf die Methode
"getData()" der Klasse "Image" an. Beispiel:
@ImageContent
public byte[] getData() {
return data;
}
Bei der Validierung des Datenattributs wird die Methode "isValid()" in
ImageContentValidator gestartet. Diese Methode enthält Logik für die Durchführung einer
einfachen Prüfung des Formats der binären Bilddaten.
Ein Feature in ImageContentValidator, das unter Umständen übersehen wird, ist die Validierung
eines bestimmten Bildtyps. Laut Definition werden die Formate
JPEG und GIF akzeptiert, aber es kann auch geprüft werden, ob ein ganz bestimmtes Format verwendet wird.
Wenn Sie die Annotation beispielsweise
in das folgende Codebeispiel ändern, können Sie den Validator dazu anweisen, nur Bilddaten mit dem gültigen JPEG-Inhalt zuzulassen.
@ImageContent(ImageType.JPEG)
public byte[] getData() {
return data;
}
Integritätsbedingungen auf
Typebene sind ebenfalls eine Option, weil Sie für eine Entität
möglicherweise Kombinationen von Attributen auswerten müssen.
In den vorherigen Beispielen werden Bean-Validation-Integritätsbedingungen für einzelne Attribute verwendet.
Integritätsbedingungen auf Typebene ermöglichen eine Validierung mehrerer Attribute.
Beispielsweise prüfen die auf die Entität "Image"
angewendeten Integritätsbedingungen, ob ein Bildtyp festgelegt
(nicht null) ist, ob die Erweiterung im Bilddateinamen unterstützt wird und ob für den angegebenen Typ
das richtige Datenformat verwendet wird.
Es wird aber beispielsweise nicht kollektiv geprüft, ob eine Datei mit dem Namen
img0.gif den Typ GIF hat und das Format der Daten für eine GIF-Bilddatei gültig ist.
Weitere Informationen zu Integritätsbedingungen auf Typebene finden Sie im White Paper
"OpenJPA Bean Validation Primer" im Abschnitt "Type-level constraints."Validierungsgruppen
Die Bean-Validierung verwendet Validierungsgruppen, um festzustellen, welche Art der Validierung wann ausgeführt wird.

Weitere Informationen zu Validierungsgruppen mit OpenJPA finden Sie im White Paper "OpenJPA Bean Validation Primer" und im Abschnitt zu den Validierungsgruppen.
JPA-Domänenmodell
Zusätzlich zur Entität "Image" gibt es die persistenten Entitäten "Album", "Creator" und "Location". Die Entität "Album" enthält eine Referenz auf die Sammlung der zugehörigen Imageentitäten. Die Entität "Creator" enthält eine Referenz auf die Album-Entitäten, die der Bildersteller (Creator) beiträgt, und eine Referenz auf die erstellten Imageentitäten. Damit können Sie alle Funktionen für die Navigation zwischen den einzelnen Entitäten in der Domäne nutzen. Dem Bild wurde eine integrierbare Position hinzugefügt, um das Speichern von Informationen zur Bildposition zusammen mit dem Bild zu ermöglichen.
private Location location;
@Valid
@Embedded
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
Die Annotation "@Valid" stellt in einer JPA-Umgebung eine Verkettungsprüfung integrierbarer Objekte bereit.
Aus diesem Grund werden beim Validieren des Bilds auch alle Integritätsbedingungen für die von ihm referenzierte
Position ausgewertet.
Wenn
@Valid nicht angegeben ist, wird die Position nicht ausgewertet.
In einer JPA-Umgebung wird die verkettete Validierung über @Valid nur für integrierbare Objekte unterstützt.
Referenzierte Entitäten und Sammlungen von Entitäten
werden getrennt voneinander validiert, um eine zirkuläre Validierung zu verhindern.Bean Validation und die JPA-Umgebung
Die Spezifikation JPA vereinfacht die Integration mit der Bean Validation API. In einer JSE-Umgebung wird Bean Validation standardmäßig aktiviert, wenn Sie die Bean Validation API und einen Bean-Validation-Provider in Ihrem Laufzeitklassenpfad bereitstellen. In einer Umgebung von Java EE enthält der Anwendungsserver einen Bean-Validierungsprovider, daher ist es nicht erforderlich, diesen in das Anwendungspaket aufzunehmen. In beiden Umgebungen muss die Datei persistence.xml ab Version 2.0 verwendet werden.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0" >
...
</persistence>
- Auto
Aktiviert Bean Validation, wenn ein Validation-Provider im Klassenpfad vorhanden ist. Auto ist die Standardeinstellung.
- Callback
Wenn der Modus "Callback" angegeben ist, muss ein Bean-Validation-Provider für den JPA-Provider verfügbar sein. Wenn nicht, löst der JPA-Provider bei der Instanziierung einer neuen JAP-Entitätsmanagerfactory eine Ausnahme aus.
- Ohne
Inaktiviert Bean Validation für eine bestimmte Persistenzeinheit.

<persistence-unit name="auto-validation">
...
<!-- Validierungsmodi: AUTO, CALLBACK, NONE -->
<validation-mode>AUTO</validation-mode>
...
</persistence-unit>
Eine andere Methode ist die Konfiguration des Validierungsmodus über das Programm
durch Angabe der Eigenschaft "javax.persistence.validation.mode" mit dem Wert
"auto", "callback" oder "none" bei der Erstellung einer neuen JPA-Entitätsmanagerfactory, wie im folgenden
Beispiel gezeigt:
Map<String, String> props = new HashMap<String, String>();
props.put("javax.persistence.validation.mode", "callback");
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("validation", props);
<persistence-unit name="non-default-validation-groups">
<class>my.Entity</class>
<validation-mode>CALLBACK</validation-mode>
<properties>
<property name="javax.persistence.validation.group.pre-persist"
value="org.apache.openjpa.example.gallery.constraint.SequencedImageGroup"/>
<property name="javax.persistence.validation.group.pre-update"
value="org.apache.openjpa.example.gallery.constraint.SequencedImageGroup"/>
<property name="javax.persistence.validation.group.pre-remove"
value="javax.validation.groups.Default"/>
</property>
</persistence-unit>
Das folgende Beispiel zeigt verschiedene Phasen des JPA-Lebenszyklus, einschließlich
"persist", "update" und "remove":EntityManagerFactory emf =
Persistence.createEntityManagerFactory("BeanValidation");
EntityManager em = emf.createEntityManager();
Location loc = new Location();
loc.setCity("Rochester");
loc.setState("MN");
loc.setZipCode("55901");
loc.setCountry("USA");
// Bild mit nicht übereinstimmenden Typ und nicht übereinstimmender Dateierweiterung erstellen
Image img = new Image();
img.setType(ImageType.JPEG);
img.setFileName("Winter_01.gif");
loadImage(img);
img.setLocation(loc);
// *** PERSIST ***
try {
em.getTransaction().begin();
// Entity mit nicht übereinstimmender Erweiterung und nicht übereinstimmendem Typ speichern
em.persist(img);
} catch (ConstraintViolationException cve) {
// Transaktion wurde für Rollback markiert. Rollback durchführen und
// neue Transaktion starten.
em.getTransaction().rollback();
em.getTransaction().begin();
// Dateityp korrigieren und Speichern wiederholen.
img.setType(ImageType.GIF);
em.persist(img);
em.getTransaction().commit();
}
// *** UPDATE ***
try {
em.getTransaction().begin();
// Dateinamen in einen nicht übereinstimmenden Dateinamen ändern
// und festschreiben, um Aktualisierung (update) auszulösen.
img.setFileName("Winter_01.jpg");
em.getTransaction().commit();
} catch (ConstraintViolationException cve) {
// Ausnahme behandeln. Festschreibung (commit) ist fehlgeschlagen,
// deshalb wurde bereits ein Rollback der Transaktion durchgeführt.
handleConstraintViolation(cve);
}
// Der Aktualisierungsfehler hat zur Freigabe des Bilds geführt. Es
// muss dem Persistenzkontext wieder hinzugefügt werden.
img = em.merge(img);
// *** REMOVE ***
em.getTransaction().begin();
try {
// Typ entfernen und festschreiben, um Entfernen auszulösen
img.setType(ImageType.GIF);
em.remove(img);
} catch (ConstraintViolationException cve) {
// Rollback the active transaction and handle the exception
em.getTransaction().rollback();
handleConstraintViolation(cve);
}
em.close();
emf.close();
Ausnahmen
In jeder Phase des JPA-Lebenszyklus können Gültigkeitsfehler auftreten.
private void handleConstraintViolation(ConstraintViolationException cve) {
Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
for (ConstraintViolation<?> cv : cvs) {
System.out.println("------------------------------------------------");
System.out.println("Violation: " + cv.getMessage());
System.out.println("Entity: " + cv.getRootBeanClass().getSimpleName());
// Der Verstoß ist in der Leaf-Bean (integrierbar) aufgetreten.
if (cv.getLeafBean() != null && cv.getRootBean() != cv.getLeafBean()) {
System.out.println("Embeddable: " +
cv.getLeafBean().getClass().getSimpleName());
}
System.out.println("Attribute: " + cv.getPropertyPath());
System.out.println("Invalid value: " + cv.getInvalidValue());
}
}
Die Verarbeitung ungültiger Integritätsbedingungen ist
in der Regel einfach, wenn Integritätsbedingungen auf Attributebene verwendet werden.
Wenn Sie einen Validator auf Typebene mit Integritätsbedingungen auf Typebene verwenden,
kann es komplizierter sein, die Attribute oder die Kombination von Attributen zu ermitteln, deren Validierung gescheitert ist.
Außerdem wird das gesamte Objekt als ungültiger Wert und nicht nur ein einzelnes Attribut zurückgegeben.
In Fällen, in denen spezielle Informationen zum Fehler benötigt werden,
können, wie in der Spezifikation "Bean Validation" beschrieben,
eine Integritätsbedingung auf Attributebene oder angepasste Informationen zur ungültigen
Integritätsbedingung
bereitgestellt werden.Beispiel
Das in diesem Artikel beschriebene Einsatzszenario für das JPA-Modell und die Anwendung für eine Bildersammlung kann über ein Beispiel implementiert werden, das im White Paper OpenJPA Bean Validation Primer beschrieben wird.