JPA 中的 Bean 验证

数据验证是在应用程序的所有层(包括持久性)中进行的普通任务。Java™ Persistence API (JPA) 为 Bean 验证 API 提供支持,以便可以在运行时完成数据验证。本主题包括一个使用方案,在此方案中,在样本数字图像库应用程序的 JPA 环境中使用 Bean 验证。

Bean 验证 API 在 Java Enterprise Edition (Java EE) 和 Java Platform, Standard Edition (JSE) 环境中提供跨技术的无缝验证。除 JPA 以外,这些技术还包括 Java Server Faces (JSF) 和 Java EE Connector Architecture (JCA)。您可以阅读“Bean 验证 API”主题中关于 Bean 验证的更多信息。

有三种 Bean 验证核心概念:约束、约束违例处理和验证器。如果您在类似 JPA 的集成环境中运行应用程序,那么无需直接与验证器连接。

验证约束是添加到 JavaBeans 组件的类、字段或方法的注解或 XML 代码。 约束可以是内置约束或者用户定义的约束。这些注释用来定义常规约束定义以及用于编写约束。内置的约束由 Bean 验证规范定义且每个验证提供程序都可以提供。有关内置约束的列表,请参阅“Bean 验证内置约束”主题。 如果您需要不同于内置约束的约束,可以构建自己的用户定义约束。

约束和 JPA

以下使用方案说明如何在样本数字图像库应用程序的 JPA 体系结构中使用内置约束。

在第一个代码示例中,将内置约束添加到称为图像的 JPA 模型的简单实体。一张图像具有标识、图像类型、文件名和图像数据。必须指定图像类型且图像文件名必须包括有效 JPEG 或 GIF 扩展名。该代码显示应用了某些内置 Bean 验证约束的注解图像实体。
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;
    }
}

图像类使用两个内置约束 @NotNull 和 @Pattern。@NotNull 约束确保指定了 ImageType 元素且 @Pattern 约束使用正则表达式模式匹配来确保在图像文件名后添加受支持图像格式后缀。每个约束具有对应的验证逻辑,此逻辑在验证图像实体时在运行时开始。 如果没有满足任一约束,那么 JPA 提供程序抛出 ConstraintViolationException 以及定义的消息。JSR-303 规范还为消息属性内变量的使用作出规定。该变量引用资源束中的键入消息。资源束支持特定于环境的消息以及对消息的全球化、翻译和多文化支持。

您可以创建自己的定制验证器和约束。在上一示例中,图像实体使用 @Pattern 约束来验证图像的文件名。但是,它没有自己检查实际图像数据的约束。您可以使用基于模式的约束;但是,您不如在您专门为检查数据的约束来创建约束时那么灵活。在此情况下,您可以构建定制方法级别约束注释。以下是称为 ImageContent 的定制或用户定义的约束。
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 };
}
下一步,您必须创建验证器类 ImageContentValidator。 在验证约束时,此验证器内的逻辑由验证提供程序实施。验证器类通过 @Constraint 注解上的 validatedBy 属性来绑定到约束注释,如以下代码中所示:
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;
/**
 * Simple check that file format is of a supported type
 */
public class ImageContentValidator implements ConstraintValidator<ImageContent, byte[]> {
    private List<ImageType> allowedTypes = null;
    /**      
     * Configure the constraint validator based on the image      
     * types it should support.      
     * @param constraint the constraint definition      
     */     
    public void initialize(ImageContent constraint) {         
        allowedTypes = Arrays.asList(constraint.value());     
    }      
    /**      
     *Validate a specified value.      
     */     
    public boolean isValid(byte[] value, ConstraintValidatorContext context) {
        if (value == null) {             
            return false;
        }
        // Verify the GIF header is either GIF87 or 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;
            }
        }
        // Verify the JPEG begins with SOI and ends with 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;
            }
        }
        // Unknown file format
        return false;     
    }
}
将该新约束应用于图像类的 getData() 方法;例如:
@ImageContent
    public byte[] getData() {
        return data;
    }
进行对数据属性的验证时,启动 ImageContentValidator 中的 isValid() 方法。此方法包含用于执行对二进制图像数据格式的简单验证的逻辑。ImageContentValidator 中可能低估的功能是它还可以为特定图像类型进行验证。按定义,它接受 JPEG 或 GIF 格式,但是它还可以为特定格式进行验证。例如,通过将注解更改为以下代码示例,指示验证器只允许具有有效 JPEG 内容的图像数据:
@ImageContent(ImageType.JPEG)
    public byte[] getData() {
        return data;
    }
类型级别约束也是注意事项,因为您可能需要验证实体上属性的组合。在先前示例中,在单个属性上使用验证约束。类型级别约束可以提供集体验证。例如,应用于图像实体的约束验证是否已设置图像类型(非空),图像文件名称的扩展名是否为受支持类型,以及数据格式对于指示的类型是否正确。但是,例如,它没有集体验证名为 img0.gif 的文件是否类型为 GIF 而数据格式是否针对有效 GIF 文件图像。 有关类型级别约束的更多信息,请参阅白皮书“OpenJPA Bean Validation Primer”和“类型级别约束”部分。

验证组

Bean 验证使用验证组来确定进行什么类型的验证以及何时进行验证。

没有要实施的特殊接口或者应用来创建验证组的注解。验证组以类定义表示。
最佳实践 最佳实践: 使用组时,使用简单接口。使用简单接口使验证组在多个环境中更可用。然而,如果类或实体定义用作验证组,它可能通过引入对应用程序无意义的域类和逻辑来污染另一应用程序的对象模型。缺省情况下,如果在单个约束上没有指定验证组或多个组,那么使用 javax.validation.groups.Default 组来予以验证。创建定制组与新建接口定义一样简单。bprac

有关使用 OpenJPA 的验证组的更多信息,请阅读白皮书《OpenJPA Bean Validation Primer》和“Validation groups”一节。

JPA 域模型

图像实体是相册、创建者和位置持久类型。相册实体包含其图像实体集合的引用。创建者实体包含图像创建者添加到的相册实体的引用以及对创建的图像实体的引用。这提供域中每个实体之间的完整浏览功能。可嵌入的位置已添加到图像来支持随图像一起存储位置信息。

相册和创建者实体具有标准内置约束。可嵌入的位置在演示如何使用 @Valid 注解来验证嵌入对象方面更独特。要将位置嵌入到图像,将新字段和相应持久性属性添加到图像类;例如:
private Location location;

    @Valid
    @Embedded
    public Location getLocation() {
        return location;
    }

    public void setLocation(Location location) {
        this.location = location;
    }
@Valid 注解提供对 JPA 环境内可嵌入对象的链式验证。因此,验证图像时,也验证其引用的位置上的任何约束。如果没有指定 @Valid,那么未验证该位置。 在 JPA 环境中,通过 @Valid 进行的链式验证仅可用于可嵌入对象。单独地验证引用的实体和实体集合以防止循环验证。

Bean 验证和 JPA 环境

JPA 规范简化了 Bean 验证 API 的集成。在 JSE 环境中,您在运行时类路径上提供 Bean 验证 API 和 Bean 验证提供程序时,缺省情况下,已启用 Bean 验证。在 Java EE 环境中,应用程序服务器包括 Bean 验证提供程序,因此无需随应用程序捆绑 Bean 验证提供程序。在这两种环境中,您都必须使用 V2.0 或更高版本的 persistence.xml 文件。

V1.0 persistence.xml 没有提供配置 Bean 验证的方式。要求使用 V2.0 或更高版本 persistence.xml 可阻止纯 JPA 1.0 应用程序产生验证启动和运行时成本。在基于 1.0 的应用程序没有用于禁用验证的标准方式的情况下,这就很重要。在 Java EE 环境中,通过修改 persistence.xml 文件的根元素在现有 1.0 应用程序中启用验证。以下示例表示 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>
Bean 验证提供 JPA 环境内的三种操作方式:
  • 自动

    如果验证提供程序在类路径内可用,就启用 Bean 验证。自动是缺省值。

  • 回调

    指定了回调方式时,Bean 验证提供程序必须可供 JPA 提供程序使用。否则,JPA 提供程序在新的 JPA 实体管理器工厂实例化时抛出异常。

  • 为特定持久性单元禁用 Bean 验证。

自动方式将简化部署,但是在验证由于配置问题而没有进行时会导致问题。
最佳实践 最佳实践: 将“无”或回调方式显式用于一致的行为。bprac
而且,如果指定“无”,那么 JPA 在启动时优化且不会尝试执行意外的验证。 在要求容器提供验证提供程序的 Java EE 环境中,显式禁用验证尤其重要。因此,除非指定,否则在容器中启动的 JPA 2.0 或更高版本应用程序已启用验证。此过程在生命周期事件期间添加附加处理。
有两种方式可在 JPA 中配置验证方式。最简单的方式是将验证方式元素添加到具有希望的验证方式的 persistence.xml 中,如以下示例中所示:
 <persistence-unit name="auto-validation">
		        	... 
				<!-- Validation modes: AUTO, CALLBACK, NONE -->
				<validation-mode>AUTO</validation-mode>
				...
		</persistence-unit> 
另一方式是通过在新建 JPA 实体管理器工厂时指定值为“自动”、“回调”或“无”的 javax.persistence.validation.mode 属性来以编程方式配置验证方式,如以下示例中所示:
Map<String, String> props = new HashMap<String, String>();
		props.put("javax.persistence.validation.mode", "callback");
        EntityManagerFactory emf = 
            Persistence.createEntityManagerFactory("validation", props);
在 JPA 生命周期事件处理期间,在 JPA 内进行了 Bean 验证。 如果已启用,在 PrePersist、PreUpdate 和 PreRemove 生命周期事件的最终阶段进行验证。由于其中某些事件可以修改正在验证的实体,所以仅在所有用户定义的生命周期事件之后进行验证。缺省情况下,JPA 为 PrePersist 和 PreUpdate 生命周期事件的缺省验证组启用验证。如果您必须验证其他验证组或为 PreRemove 事件启用验证,那么您可以指定验证组验证 persistence.xml 中的每个生命周期事件,如以下示例中所示:
<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>
以下示例显示 JPA 生命周期的各种阶段,包括保存、更新和移除:
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");

        // Create an Image with non-matching type and file extension
        Image img = new Image();
        img.setType(ImageType.JPEG);
        img.setFileName("Winter_01.gif");
        loadImage(img);
        img.setLocation(loc);
        
        // *** PERSIST ***
        try {
            em.getTransaction().begin();
            // Persist the entity with non-matching extension and type
            em.persist(img);
        } catch (ConstraintViolationException cve) {
            // Transaction was marked for rollback, roll it back and
            // start a new one
            em.getTransaction().rollback();
            em.getTransaction().begin();
            // Fix the file type and re-try the persist.
            img.setType(ImageType.GIF);
            em.persist(img);
            em.getTransaction().commit();
        }

        // *** UPDATE ***
        try {
            em.getTransaction().begin();
            // Modify the file name to a non-matching file name 
            // and commit to trigger an update
            img.setFileName("Winter_01.jpg");
            em.getTransaction().commit();
        }  catch (ConstraintViolationException cve) {
            // Handle the exception.  The commit failed so the transaction
            // was already rolled back.
            handleConstraintViolation(cve);
        }
        // The update failure caused img to be detached. It must be merged back 
        // into the persistence context.
        img = em.merge(img);

        // *** REMOVE ***
        em.getTransaction().begin();
        try {
            // Remove the type and commit to trigger removal
            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();

异常

在 JPA 生命周期的任何部分中都可能发生验证错误。

如果一个或多个约束在生命周期事件期间验证失败,那么 JPA 提供程序抛出 ConstraintViolationException。JPA 提供程序抛出的 ConstraintViolationException 包括发生的一组 ConstraintViolation。 单个约束违例包含关于约束的信息,包括:消息、根 Bean 或 JPA 实体,在验证 JPA 可嵌入对象时有用的叶 Bean、验证失败的属性以及导致失败的值。以下是异常处理例程样本:
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());
          	// The violation occurred on a leaf bean (embeddable)
          	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());
      }
    }
在使用属性级别约束时,约束违例处理一般很简单。如果您将类型级别验证器用于类型级别约束,那么可能更难以确定哪个属性或属性组合验证失败。 而且,将整个对象返回为无效值,而不是单个属性。在需要具体故障信息的情况下,可能如 Bean 验证规范中所述,提供对属性级别约束的使用或定制约束违例。

样本

可以通过在白皮书 OpenJPA Bean Validation primer 中提供的样本来实施本主题中提供的 JPA 模型和图像库应用程序使用方案。


指示主题类型的图标 概念主题



时间戳记图标 最近一次更新时间: last_date
http://www14.software.ibm.com/webapp/wsbroker/redirect?version=cord&product=was-nd-mp&topic=cdat_beanvaljpa
文件名:cdat_beanvaljpa.html