Bean-Validation mit JEE6 - Java Enterprise Edition 6

03.10.2012

Bean-Validation mit JEE6 - JSR-303

In diesem Artikel wird beschrieben was unter Bean-Validation zu verstehen ist und wie es einsetzt wird, um Werte zu validieren. D.h. es wird überprüft, ob übergebenen Funktionsparameter bestimmten Regeln entsprechen.

In dem folgenden Beispiel wird eine JSF-Seite mit einem Eingabefeld und einem Button erstellt. In das Eingabefeld kann eine ISBN-Nummer eingetragen und durch ein Klick auf den Button in eine Datenbank geschrieben werden. Bevor der Wert abgelegt wird, soll er allerdings validiert werden, damit keine inkonsistenten Daten in der Datenbank landen.

Das Eingabefeld der JSF-Seite ist mit einem Presentation-Model (Backing-Bean) verbunden, das den Zugriff auf eine Entity ermöglicht. Der Button löst beim Klick einen Funktionsaufruf zum Speichern des Werts aus. Auch dieser läuft über das Presentation-Model (Backing-Bean).

Die Validierung passiert in der Entity und wird mittels Annotationen vorgenommen.

Falls die Architektur dieser kleinen "Anwendung" unklar sein sollte, dann kann man in dem JEE6-Tutorial weitere Informationen und Erklärungen zu dem Thema finden.

Als erstes legen wir eine Entity an, die eine id und eine ISBN-Nummer besitzt. Wichtig sind die Annotationen @NotNull und @Size. Einerseits wird damit festgelegt, dass die ISBN-Nummer nicht leer sein darf und die Länge mindestens 18 Zeichen, aber maximal 30 Zeichen lang sein darf. Außerdem wird eine Fehlermeldung definiert, die angezeigt wird, wenn der Eingabewert ungültig ist. Es ist anzumerken, dass die Überprüfung auf @NotNull redundant ist und in einer realen Anwendung weggelassen werden kann.

package org.hameister.isbn;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

/**
 *
 * @author Hameister
 */
@Entity
public class ISBN implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Size(min = 18, max = 30, message="ISBN-Nummer hat die falsche Länge! Der die Länge muss zwischen 18 und 30 Zeichen liegen.")
    private String isbn;

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

Falls man die Entity nicht mit einem Wizard, wie beispielsweise bei NetBeans möglich, anlegt, dann darf man die Datei persistence.xml nicht vergessen.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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">
  <persistence-unit name="ISBNValidationDemoPU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/sample</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

Außerdem wird noch eine Stateless Session Bean benötigt. Diese besitzt eine Methode addISBN, die die Entity über den Entity-Manager in die Datenbank schreibt.

package org.hameister.isbn;


import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 *
 * @author Hameister
 */

@Stateless
public class ISBNStore {

    @PersistenceContext
    EntityManager em;

     public void addISBN(ISBN entity) {
        em.persist(entity);
        System.out.println("Added:"+ entity.getIsbn());
    }
}

Hinweis: Ggf. muss noch eine Datei beans.xml mit folgendem Inhalt angelegt werden:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

Das Presentation-Model sieht folgendermaßen aus:

package org.hameister.isbn;

import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;

/**
 *
 * @author Hameister
 */
@Named
@RequestScoped
public class IsbnPM {
    private ISBN isbn = new ISBN();

    @EJB
    ISBNStore bean;

    public ISBN getEntity() {
        return isbn;
    }

    public void setEntity(ISBN entity) {
        this.isbn = entity;
    }

    public void store() {
        bean.addISBN(isbn);
    }
}

Es wird mittels der Annotation @EJB die Session Bean injeziert, so dass in der Methode store die ISBN-Nummer gespeichert werden kann. Außerdem wird für das erstellte Objekt vom Typ ISBN ein getter und ein setter benötigt.

Die JSF-Seite enthält ein Eingabefeld inputText und einen Button commandButton, die, wie weiter oben beschrieben, verbunden sind.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>ISBN-Validation</title>
    </h:head>
    <h:body>
        <h:form>
            ISBN:
            <h:inputText value="#{isbnPM.entity.isbn}"/>
            <h:commandButton value="Add" action="#{isbnPM.store()}" />
        </h:form>
    </h:body>
</html>

Nachdem wir das Beispiel in ein war-Archiv gepackt und auf einem Application-Server deployt haben, wird folgendes im Browser zu sehen sein, wenn eine invalide ISBN-Nummer eingetragen wird:

Create New Project

In dem Beispiel wurde eine sehr einfach Regel überprüft. Es ist allerdings auch möglich komplexere Regeln mittels regulärer Ausdrücke zu überprüfen. Dafür kann die Annotation @Pattern verwendet werden. Den folgenden regulären Ausdruck habe ich auf der Seite GeekZilla gefunden.

package org.hameister.isbn;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

/**
 *
 * @author Hameister
 */
@Entity
public class ISBN implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Pattern(regexp = "ISBN\\x20(?=.{13}$)\\d{1,5}([- ])\\d{1,7}\\1\\d{1,6}\\1(\\d|X)$", message="Die ISBN-Nummer hat das falsche Format. Ein eine gültige Eingabe sieht bespielsweise folgendermaßen aus: ISBN 1-56389-668-0")
    private String isbn;

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

Neben den drei angesprochenen Annotation zum Validieren existieren noch weitere Möglichkeiten die Gültigkeit eines Wertes zu überprüfen. Beispielsweise: AssertFalse, @AssertTrue, @DecimalMax, @DecimalMin, @Digits, @Future, @Max, @Min, @NotNull, @Null, @Past, @Pattern, @Size

Genauere Erklärungen dazu findet man in folgendem Dokument von Oracle: Using Bean Validation

Weitere Informationen

Die Bean Validation 1.0 ist in JSR-303 beschrieben. Hier findet man das PDF mit der Spezifikation dazu.