JPA의 Bean 유효성 검증

데이터 유효성 검증은 지속과 함께 모든 애플리케이션 계층에서 수행되는 공통 태스크입니다. JPA(Java™ Persistence API)는 런타임 시에 데이터 유효성 검증이 수행될 수 있도록 Bean 유효성 검증 API에 대한 지원을 제공합니다. 이 주제에는 Bean 유효성 검증이 샘플 디지털 이미지 갤러리 애플리케이션의 JPA 환경에서 사용되는 사용법 시나리오가 포함됩니다.

Bean 유효성 검증 API는 Java EE(Java Enterprise Edition) 및 JSE(Java Platform, Standard Edition) 환경이 기술에서 끊임 없는 유효성 검증을 제공합니다. JPA뿐만 아니라 이 기술은 JSF(JavaServer Faces) 및 JCA(Java EE Connector Architecture)도 포함합니다. Bean 유효성 검증 API 주제에서 Bean 유효성 검증에 대한 추가 정보를 읽을 수 있습니다.

Bean 유효성 검증에는 세 개의 핵심 개념인 제한조건, 제한조건 위반 처리, 유효성 검증기가 있습니다. JPA와 같은 통합 환경에서 애플리케이션을 실행 중인 경우 유효성 검증기와 직접 인터페이스할 필요가 없습니다.

유효성 검증 제한조건은 JavaBeans 컴포넌트의 클래스, 필드 또는 메소드에 추가되는 어노테이션 또는 XML 코드입니다. 제한조건은 빌드되거나 사용자 정의될 수 있습니다. 이는 정규 제한조건 정의 정의 및 제한조건 작성에 사용됩니다. 내장 제한조건은 Bean 유효성 검증 스펙으로 정의되며 모든 유효성 검증 제공자에 사용할 수 있습니다. 내장 제한조건의 목록은 Bean 유효성 검증 내장 제한조건 주제를 참조하십시오. 내장 제한조건과 다른 제한조건이 필요한 경우에는 자체적으로 사용자 정의 제한조건을 빌드할 수 있습니다.

제한조건 및 JPA

다음 사용 시나리오는 내장 제한조건이 샘플 디지털 이미지 갤러리 애플리케이션의 JPA 아키텍처에서 사용되는 방법에 대해 설명합니다.

첫 번째 코드 예제에서 내장 제한조건은 이미지라고 하는 JPA 모델의 단순 엔티티에 추가됩니다. 이미지에는 ID, 이미지 유형, 파일 이름, 이미지 데이터가 포함됩니다. 이미지 유형을 지정해야 하며 이미지 파일 이름의 확장자는 유효한 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라고 하는 사용자 정의(custom) 또는 사용자가 정의하는(user-defined) 제한조건입니다.
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() 메소드가 시작됩니다. 이 메소드에는 2진 이미지 데이터 형식에 대한 단순한 유효성 검증을 수행하는 논리가 포함됩니다. ImageContentValidator에 대해 잠재적으로 간과된 기능은 특정 이미지 유형을 유효성 검증할 수도 있다는 점입니다. 당연히 JPEG 또는 GIF 형식도 허용하지만 특정 형식에 대한 유효성도 검증할 수 있습니다. 예를 들어, 다음 코드 예에 대한 어노테이션을 변경하여 유효성 검증기는 유효한 JPEG 컨텐츠의 이미지 데이터만 허용하도록 지시됩니다.
@ImageContent(ImageType.JPEG)
    public byte[] getData() {
        return data;
    }
엔티티에서 속성 조합도 유효성 검증해야 할 수 있기 때문에 유형 레벨 제한조건도 고려해야 합니다. 이전 예에서 유효성 검증 제한조건이 개별 속성에서 사용되었습니다. 유형 레벨 제한조건을 통해 집합적인 유효성 검증을 제공할 수 있습니다. 예를 들어, 이미지 엔티티에 적용된 제한조건은 이미지 유형이 설정되었는지(널이 아님), 이미지 파일 이름의 확장자는 지원되는 유형인지, 데이터 형식이 표시된 유형에 대해 맞는지를 유효성 검증합니다. 그렇지만 img0.gif 이름의 파일이 GIF 유형인지 및 데이터 형식이 유효한 GIF 파일 이미지인지를 일괄하여 유효성 검증하지는 않습니다. 유형 레벨 제한조건에 대한 자세한 정보에 대해서는 백서, OpenJPA Bean Validation Primer 및 "Type-level constraints" 절을 참조하십시오.

유효성 검증 그룹

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 유효성 검증은 런타임 클래스 경로에 Bean 유효성 검증 API 및 Bean 유효성 검증 제공자를 제공하면 기본적으로 사용됩니다. Java EE 환경에서, 애플리케이션 서버에는 Bean 유효성 검증 제공자를 포함하여 이를 애플리케이션에 번들할 필요가 없습니다. 두 환경 모두에서 버전 2.0 이상의 persistence.xml 파일을 사용해야 합니다.

버전 1.0 persistence.xml은 Bean 유효성 검증 구성 방법을 제공하지 않습니다. 버전 2.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 엔티티 관리자 팩토리 인스턴스화 시에 예외를 처리합니다.

  • None

    특정 지속성 단위에 대해 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 내에서의 Bean 유효성 검증은 JPA 라이프사이클 이벤트 처리 중에 발생합니다. 사용하는 경우, 유효성 검증은 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에는 발생한 ConstraintViolations 세트가 포함됩니다. 개별 제한조건 위반에는 다음을 포함하여 제한조건에 관련된 정보가 포함됩니다. 메시지, 루트 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 유효성 검증 스펙에서 설명하는 대로 제공될 수 있습니다.

샘플

이 주제에서 제공되는 JPA 모델 및 이미지 갤러리 애플리케이션 사용 시나리오는 백서 OpenJPA Bean Validation primer에서 제공되는 샘플을 통해 구현 가능합니다.


주제 유형을 표시하는 아이콘 개념 주제



시간소인 아이콘 마지막 업데이트 날짜: last_date
http://www14.software.ibm.com/webapp/wsbroker/redirect?version=cord&product=was-nd-mp&topic=cdat_beanvaljpa
파일 이름:cdat_beanvaljpa.html