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 아키텍처에서 사용되는 방법에 대해 설명합니다.
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 스펙은 메시지 속성 내에서 변수 사용을 위한 프로비저닝도 수행합니다. 변수는 자원 번들에서 입력된 메시지를 참조합니다. 자원 번들은 환경별 메시지 및 다국어 지원, 변환, 메시지에 대한 다문화 지원을 지원합니다.
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 유효성 검증은 유효성 검증 그룹을 사용하여 유효성 검증 유형 및 유효성 검증 수행 시기를 판별합니다.

OpenJPA를 포함한 유효성 검증 그룹에 대한 자세한 정보는 백서, OpenJPA Bean Validation Primer 및 "Validation groups" 절을 참조하십시오.
JPA 도메인 모델
이미지뿐만 아니라 엔티티는 앨범, 작성자, 위치 지속성 유형도 있습니다. 앨범 엔티티는 해당 이미지 엔티티에 대한 콜렉션을 참조합니다. 작성자 엔티티에는 이미지 작성자가 컨트리뷰션하는 앨범 엔티티에 대한 참조와 작성된 이미지 엔티티에 대한 참조도 포함됩니다. 이는 도메인의 각 엔티티에 대한 완벽한 탐색 가능 기능을 제공합니다. 임베드 가능한 위치가 이미지가 포함된 위치 정보 저장을 지원하기 위해 이미지에 추가되었습니다.
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 파일을 사용해야 합니다.
<?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 유효성 검증을 사용합니다. 자동이 기본값입니다.
- 콜백
콜백 모드가 지정되면 Bean 유효성 검증 제공자는 JPA 제공자에서 사용할 수 있어야 합니다. 그렇지 않으면 JPA 제공자가 새 JPA 엔티티 관리자 팩토리 인스턴스화 시에 예외를 처리합니다.
- None
특정 지속성 단위에 대해 Bean 유효성 검증을 사용하지 않습니다.

<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);
<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 라이프사이클의 모든 파트에서 발생할 수 있습니다.
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에서 제공되는 샘플을 통해 구현 가능합니다.