JPA에서 Bean 유효성 검증
데이터 유효성 검증은 지속성을 포함하여 모든 애플리케이션 계층에서 발생하는 공통 태스크입니다. JPA (Java™ Persistence API) 는 런타임 시 데이터 유효성 검증을 수행할 수 있도록 Bean 유효성 검증 API에 대한 지원을 제공합니다. 이 주제에는 샘플 디지털 이미지 갤러리 애플리케이션의 JPA 환경에서 Bean 유효성 검증이 사용되는 사용법 시나리오가 포함되어 있습니다.
Bean 유효성 검증 API는 Java Enterprise Edition (Java EE ) 의 기술 간에 완벽한 유효성 검증을 제공합니다. 및 JSE (Java Platform, Standard Edition ) 환경에서 사용할 수 있습니다. JPA외에도 이러한 기술에는 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;
}
}
Image 클래스에서 두 개의 기본 제공 제한조건(@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 };
}
Next,
you must create the validator class, ImageContentValidator. The logic within this validator gets
implemented by the validation provider when the constraint is validated. The validator class is
bound to the constraint annotation through the validatedBy attribute on the @Constraint annotation
as shown in the following
code: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 문서 및 "유형 레벨 제한조건" 절을 참조하십시오.유효성 검증 그룹
Bean 유효성 검증은 유효성 검증 그룹을 사용하여 유효성 검증 유형과 유효성 검증 발생 시기를 판별합니다.
OpenJPA를 포함하는 유효성 검증 그룹에 대한 자세한 정보는 OpenJPA Bean Validation Primer 문서 및 "유효성 검증 그룹" 절을 참조하십시오.
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 유효성 검증을 사용합니다. Auto가 기본값입니다.
- 콜백
콜백 모드가 지정될 때, Bean 유효성 검증 제공자는 JPA 제공자에서 사용 가능해야 합니다. 그렇지 않다면, JPA 제공자는 새 JPA 엔티티 관리자 팩토리의 인스턴스화에 예외를 처리합니다.
- 없음
특정 지속성 단위에 대해 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();
Exceptions
유효성 검증 오류는 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 문서에서 제공하는 샘플을 통해 구현할 수 있습니다.