Validación de beans en JPA
La validación de datos es una tarea común que se produce en todas las capas de una aplicación, incluida la persistencia. La Java™ Persistence API (JPA) proporciona soporte para la API de validación de bean para que la validación de datos se pueda realizar durante el tiempo de ejecución. En este tema se incluye un caso de uso en que la validación de beans se utiliza en el entorno de JPA de una aplicación de galería de imágenes digital de muestra.
La API de validación de bean proporciona una validación sin fisuras entre tecnologías en entornos Java Enterprise Edition (Java EE ) y Java Platform, Standard Edition (JSE). Además de JPA, estas tecnologías incluyen JavaServer Faces (JSF) y Java EE Connector Architecture (JCA). Puede obtener más información sobre la validación de beans en el tema API de validación de beans.
Hay tres conceptos principales de la validación de beans: las restricciones, el manejo de las infracciones de restricciones y el validador. Si está ejecutando aplicaciones en un entorno integrado como JPA, no hay necesidad de establecer una interfaz directamente con el validador.
Las restricciones de validación son anotaciones o código XML que se añaden a una clase, un campo o método de un componente de JavaBeans. Las restricciones pueden ser incorporadas o definidas por el usuario. Se utilizan para definir las definiciones de restricciones regulares y para crear las restricciones. Las restricciones incorporadas las define la especificación de validación de beans y están disponibles con cada proveedor de validación. Para obtener una lista de restricciones incorporadas, consulte el tema Restricciones incorporadas de validación de beans. Si necesita una restricción distinta de la restricciones incorporadas, puede crear sus propias restricciones definidas por el usuario.
Restricciones y JPA
El siguiente caso de uso ilustra cómo se utiliza una restricción incorporada en la arquitectura JPA de una aplicación de galería de imágenes digital de muestra.
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="Se debe especificar el tipo de imagen.")
@Enumerated(EnumType.STRING)
public ImageType getType() {
return type;
}
public void setType(ImageType type) {
this.type = type;
}
@Pattern(regexp = ".*\\.jpg|.*\\.jpeg|.*\\.gif",
message="Solamente se da soporte a las imágenes de tipos JPEG o GIF.")
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;
}
}
La clase Imagen utiliza dos restricciones incorporadas, @NotNull y @Pattern. La restricción @NotNull garantiza que se haya especificado un elemento ImageType y la restricción @Pattern utiliza la coincidencia de patrón de expresión regular para garantizar que el nombre de archivo de la imagen es el sufijo de un formato de imagen soportado. Cada restricción tiene la lógica de validación correspondiente que se inicia en tiempo de ejecución cuando se valida la entidad de la imagen. Si alguna restricción no se cumple, el proveedor JPA genera una excepción ConstraintViolationException con el mensaje definido. La especificación JSR-303 también realiza provisiones para el uso de una variable en el atributo de mensajes. La variable hace referencia a un mensaje en clave en un paquete de recursos. El paquete de recursos da soporte a mensajes específicos del entorno y a la globalización, traducción y soporte multicultural de mensajes.
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 "Los datos de imagen no tienen un formato soportado.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
ImageType[] value() default { ImageType.GIF, ImageType.JPEG };
}
A continuación, debe crear la clase del validador, ImageContentValidator.
La lógica de este validador se ve implementada por el proveedor de validación cuando se valida la restricción. La clase de validador se enlaza a la anotación de la restricción mediante el atributo validatedBy en la anotación @Constraint tal como se muestra en el código siguiente: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;
/**
* Realice una simple comprobación de que el formato del archivo sea un tipo soportado
*/
public class ImageContentValidator implements ConstraintValidator<ImageContent, byte[]> {
private List<ImageType> allowedTypes = null;
/**
* Configure el validador de la restricción en función de los
* tipos de imagen que debe soportar.
* @param restringe la definición de restricción
*/
public void initialize(ImageContent constraint) {
allowedTypes = Arrays.asList(constraint.value());
}
/**
*Validar un valor especificado.
*/
public boolean isValid(byte[] value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
// Verificar si la cabecera GIF es GIF87 o GIF89
if (allowedTypes.contains(ImageType.GIF)) {
String gifHeader = new String(value, 0, 6);
if (value.length >= 6 &&
(gifHeader.equalsIgnoreCase("GIF87a") ||
gifHeader.equalsIgnoreCase("GIF89a"))) {
return true;
}
}
// Verificar si JPEG comienza con SOI y termina con EOI
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;
}
}
// Formato de archivo desconocido
return false;
}
}
Aplique esta nueva restricción al método getData() en la clase Imagen; por ejemplo: @ImageContent
public byte[] getData() {
return data;
}
Cuando se produce la validación del atributo de datos, se inicia el método isValid() de ImageContentValidator. Este método contiene la lógica para realizar la validación simple del formato de los datos de imagen binaria. Una característica que se puede pasar por alto en
ImageContentValidator es que también puede validarse para un tipo de imagen específica. Por definición, se aceptan los formatos JPEG o GIF, pero también es posible realizar la validación para un formato específico. Por ejemplo, cambiando la anotación en el siguiente ejemplo de código, se indica al validador que sólo se permitan los datos de imágenes con un contenido JPEG válido:@ImageContent(ImageType.JPEG)
public byte[] getData() {
return data;
}
Las restricciones Tipo-nivel también hay que tenerlas en cuenta porque tal vez tenga que validar combinaciones de atributos en una entidad. En los ejemplos anteriores las restricciones de validación se han utilizado en atributos individuales. Las restricciones de tipo de nivel permiten que se proporcione una validación colectiva. Por ejemplo, las restricciones aplicadas a la entidad de imagen aseguran que se establece un tipo de imagen (no nulo), que la extensión del nombre de archivo de la imagen es de un tipo soportado y que el formato de datos es correcto para el tipo indicado. Pero, por ejemplo, no se valida en conjunto que un archivo denominado img0.gif sea del tipo GIF y el formato de los datos sea de una imagen de archivo GIF válida. Para obtener más información acerca de las restricciones de tipo de nivel, consulte el documento técnico,
OpenJPA Bean Validation Primer, y la sección acerca de las restricciones de tipo de nivel.Grupos de validación
La validación de beans utiliza grupos de validación para determinar el tipo de validación y cuándo se produce la validación.

Si desea más información sobre grupos de validación con OpenJPA, consulte el documento técnico, OpenJPA Bean Validation Primer, y la sección "Validation groups."
Modelo de dominio JPA
Además de la entidad Imagen están los tipos persistentes Álbum, Creador y Ubicación. Una entidad Álbum contiene una referencia a una colección de sus entidades Imagen. La entidad Creador contiene una referencia a las entidades de álbum con las que ha contribuido el Creador de imágenes y una referencia a las entidades Imagen creadas. Así se proporcionan prestaciones completas de navegación en cada entidad del dominio. Se ha añadido a la imagen una ubicación incorporable para dar soporte al almacenamiento de la información de ubicación con la imagen.
private Location location;
@Valid
@Embedded
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
La anotación @Valid proporciona una validación encadenada de los objetos incorporables en un entorno JPA. Por consiguiente, cuando se valida una imagen, las restricciones de la ubicación a la que hace referencia también se validan. Si no se especifica @Valid, no se validará la ubicación.
En un entorno de JPA, la validación encadenada mediante @Valid solamente está disponible para objetos incorporables. Las entidades y colecciones de entidades referidas se validan por separado para evitar una validación circular. Validación de beans y el entorno JPA
La especificación JPA realiza la integración con la API de validación de bean simple. En un entorno JSE, la validación de beans se habilita de forma predeterminada cuando se proporciona la API Validación de beans y un proveedor de validación de beans en la classpath en tiempo de ejecución. En un entorno Java EE, el servidor de aplicaciones incluye un proveedor de valdiación de bean, así que no es necesario empaquetarlo con la aplicación. En ambos entornos, debe utilizar una versión 2.0 o posterior del archivo persistence.xml.
<?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
Habilita la validación de beans si un proveedor de validación está disponible en la classpath. Auto es el valor predeterminado.
- Retorno de llamada
Cuando se especifica la modalidad de retorno de llamada, un proveedor de validación de beans debe estar disponible para por el proveedor JPA lo utilice. De no ser así, el proveedor JPA emite una excepción tras la creación de la instancia de una fábrica del gestor de entidades JPA nueva.
- Ninguna
Inhabilita la validación de beans para una unidad de persistencia en particular.

<persistence-unit name="auto-validation">
...
<!-- Modalidades de validación: AUTO, CALLBACK, NONE -->
<validation-mode>AUTO</validation-mode>
...
</persistence-unit>
La otra forma es configurar la modalidad de validación mediante programación especificando la propiedad javax.persistence.validation.mode con el valor automático, retorno de llamada o ninguno al crear una fábrica de gestión de entidades JPA nueva como se muestra en el ejemplo siguiente: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>
En el ejemplo siguiente se muestran varias fases del ciclo de vida de JPA, incluidas la persistencia, la actualización y la eliminación: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");
// Crear una Imagen con el tipo no coincidente y la extensión de archivo
Image img = new Image();
img.setType(ImageType.JPEG);
img.setFileName("Winter_01.gif");
loadImage(img);
img.setLocation(loc);
// *** PERSIST ***
try {
em.getTransaction().begin();
// Persistir la entidad con la extensión y el tipo no coincidentes
em.persist(img);
} catch (ConstraintViolationException cve) {
// La transacción se ha marcado para la retrotracción, retrotráigala e
// inicie una nueva
em.getTransaction().rollback();
em.getTransaction().begin();
// Solucione el tipo de archivo y vuelva a intentar la acción de persistir.
img.setType(ImageType.GIF);
em.persist(img);
em.getTransaction().commit();
}
// *** UPDATE ***
try {
em.getTransaction().begin();
// Modifique el nombre de archivo por un nombre de archivo no coincidente
// y confirme desencadenar una actualización
img.setFileName("Winter_01.jpg");
em.getTransaction().commit();
} catch (ConstraintViolationException cve) {
// Maneje la excepción. La confirmación ha fallado por lo que la transacción
// ya se había retrotraído.
handleConstraintViolation(cve);
}
// El error de actualización ha hecho que se desconecte img. Deberá volverla a fusionar
// en el contexto de persistencia.
img = em.merge(img);
// *** REMOVE ***
em.getTransaction().begin();
try {
// Elimine el tipo y confirme que se desencadene la eliminación
img.setType(ImageType.GIF);
em.remove(img);
} catch (ConstraintViolationException cve) {
// Retrotraiga la transacción activa y maneje la excepción
em.getTransaction().rollback();
handleConstraintViolation(cve);
}
em.close();
emf.close();
Excepciones
Los errores de validación se pueden producir en cualquier parte del ciclo de vida de JPA.
private void handleConstraintViolation(ConstraintViolationException cve) {
Set<ConstraintViolation<?>> cvs = cve.getConstraintViolations();
for (ConstraintViolation<?> cv : cvs) {
System.out.println("------------------------------------------------");
System.out.println("Violación: " + cv.getMessage());
System.out.println("Entidad: " + cv.getRootBeanClass().getSimpleName());
// La violación se ha producido en un bean de hoja (incorporable)
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());
}
}
El proceso de violaciones suele ser simple cuando se utilizan restricciones a nivel de atributo. Si utiliza un validador de nivel de nivel de tipo con restricciones de nivel de tipo, puede ser más difícil determinar qué atributo o combinación de atributos no ha sido posible validar.
Además, el objeto completo se devuelve como valor no válido en lugar de un atributo individual. En los casos en que sea necesaria la información específica del error, es posible utilizar una restricción de nivel de atributo o una violación personalizada de restricciones puede proporcionar, tal como se describe en la especificación de validación de beans. Ejemplo
El modelo JPA y el caso de ejemplo de uso de la aplicación de galería de imágenes que se proporciona en este tema se pueden implementar a través de un ejemplo que se proporciona en el documento técnico, OpenJPA Bean Validation primer.