JPA での Bean Validation
データの妥当性検査は、パーシスタンスを含む、アプリケーションのすべての層で発生する共通のタスクです。 Java™ Persistence API (JPA) は、実行時にデータ検証を行えるように、Bean Validation API をサポートします。このトピックには、Bean Validation がサンプルのデジタル・イメージ・ギャラリー・アプリケーションの JPA 環境内で使用されるという使用法の、シナリオが記載されています。
Bean Validation API は、Java Enterprise Edition (Java EE) 環境および Java Platform, Standard Edition (JSE) 環境上のテクノロジーにわたるシームレスな検証を実現します。JPA のほか、これらのテクノロジーには、JavaServer Faces (JSF) や Java EE コネクター・アーキテクチャー (JCA) が含まれます。Bean Validation について詳しくは、『Bean Validation API』トピックを参照してください。
Bean Validation には、制約、制約違反の処理、およびバリデーターという 3 つの中核概念があります。 JPA のような統合環境内でアプリケーションを実行する場合は、バリデーターと直接やりとりする必要はありません。
検証制約は、JavaBeans コンポーネントのクラス、フィールド、またはメソッドに追加されたアノテーションまたは XML コードです。 制約には、組み込み制約とユーザー定義制約があります。 これらは、通常の制約定義を定義するため、および制約を構成するために使用されます。 組み込みの制約は、Bean Validation 仕様で定義されていて、あらゆる妥当性検査プロバイダーで使用できます。 組み込みの制約のリストについては、『Bean Validation の組み込みの制約』トピックを参照してください。 組み込みの制約と異なる制約を必要とする場合は、独自のユーザー定義制約を作成することができます。
制約と 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 クラスでは、2 つの組み込みの制約 @NotNull および @Pattern が使用されています。 @NotNull 制約により ImageType エレメントが指定されることが保証され、また @Pattern 制約により正規表現のパターン・マッチングが使用され、イメージ・ファイル名にサポートされるイメージ形式の接尾部が付けられることが保証されます。 各制約には、Image エンティティーが妥当性検査される実行時に開始される、対応する妥当性検査ロジックがあります。 制約が満たされない場合、JPA プロバイダーにより定義されたメッセージとともに ConstraintViolationException がスローされます。 また、JSR-303 仕様では、message 属性内での変数の使用の規定が設けられています。 変数では、リソース・バンドル内のキー付きメッセージを参照します。 リソース・バンドルでは、環境固有のメッセージ、ならびにメッセージのグローバリゼーション、変換、および多文化がサポートされます。
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;
}
}
この新規制約を Image クラスの getData() メソッドに適用します。例えば、以下のようにします。
@ImageContent
public byte[] getData() {
return data;
}
data 属性の妥当性検査が実行されると、ImageContentValidator 内の isValid() メソッドが開始されます。
このメソッドには、バイナリー・イメージ・データ形式の簡単な妥当性検査を実行するためのロジックが含まれています。
ImageContentValidator 内にある見逃されがちな機能は、特定のイメージ・タイプに対する妥当性検査もできる機能です。
定義上 JPEG 形式または GIF 形式が受け付けられますが、特定の形式に対する妥当性検査を行うこともできます。
例えば、次のコード例にアノテーションを変更すると、バリデーターは有効な JPEG コンテンツを持つイメージ・データしか許可しないように指示されます。
@ImageContent(ImageType.JPEG)
public byte[] getData() {
return data;
}
タイプ・レベルの 制約も 1 つの考慮事項です。
これは、1 つのエンティティー上の属性の組み合わせを妥当性検査することが必要になる場合があるためです。
前の例では、妥当性検査制約は個々の属性に対して使用されています。
タイプ・レベルの制約では、一括の妥当性検査を提供することが可能になります。
例えば、image エンティティーに適用されるこの制約では、イメージ・タイプが設定されているかどうか (非ヌルであるかどうか)、イメージ・ファイル名の拡張子がサポートされているタイプのものか、および指示されているタイプに対してデータ・フォーマットが正しいかどうかについて、検証を行います。
しかし、例えば、img0.gif と名付けられたファイルのタイプが GIF であること、データのフォーマットが有効な GIF ファイル・イメージ用のものであることが一括しては妥当性検査されません。
タイプ・レベルの制約について詳しくは、ホワイト・ペーパーの「OpenJPA Bean Validation Primer」のセクション『Type-level constraints』を参照してください。妥当性検査グループ
Bean Validation では、検証グループを使用して、どのタイプの検証をいつ実行するのかを判別します。

OpenJPA の妥当性検査グループについて詳しくは、ホワイト・ペーパー「OpenJPA Bean Validation Primer」の『Validation groups』セクションを参照してください。
JPA ドメイン・モデル
Image エンティティーに加えて、Album、Creator、および Location のパーシスタント・タイプがあります。 Album エンティティーには、その Image エンティティーのコレクションへの参照が含まれています。 Creator エンティティーには、イメージ Creator が提供した Album エンティティーへの参照、および作成された Image エンティティーへの参照が含まれています。 これにより、ドメイン内の各エンティティー相互の、すべてのナビゲーション機能が提供されます。 Image と共にロケーション情報を保管するための機能をサポートするために、組み込み可能な Location が Image に追加されています。
private Location location;
@Valid
@Embedded
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
@Valid アノテーションは、JPA 環境内の組み込み可能オブジェクトに対するチェーニングされた検証を提供します。
したがって、イメージの検証が実行される際には、そのイメージが参照するロケーションに対する制約の検証も実行されます。
@Valid を指定しないと、Location は妥当性検査されません。
JPA 環境では、@Valid による、チェーニングされた妥当性検査しか組み込み可能オブジェクトで使用できません。
エンティティーの参照されるエンティティーとコレクションは、循環する妥当性検査を行わないように、別々に妥当性検査されます。Bean Validation と JPA 環境
JPA 仕様により、Bean Validation API との統合が容易になります。JSE 環境では、ランタイム・クラスパスに Bean Validation API と Bean Validation プロバイダーを提供している場合、Bean Validation はデフォルトで使用可能になります。 Java EE 環境では、アプリケーション・サーバーに Bean Validation プロバイダーが含まれているため、これをアプリケーションにバンドルする必要はありません。いずれの環境でも、バージョン 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 Validation が使用可能になります。 「AUTO」 (自動) がデフォルトです。
- コールバック
CALLBACK (コールバック) モードを指定する場合、Bean Validation プロバイダーが JPA プロバイダーで使用可能になっている必要があります。 妥当性検査プロバイダーが使用可能になっていない場合、新規 JPA エンティティー・マネージャー・ファクトリーのインスタンス化時に、JPA プロバイダーにより例外がスローされます。
- なし
Bean Validation を特定のパーシスタンス・ユニットに対して使用不可にします。

<persistence-unit name="auto-validation">
...
<!-- Validation modes: AUTO, CALLBACK, NONE -->
<validation-mode>AUTO</validation-mode>
...
</persistence-unit>
もう 1 つの方法は、新規の JPA エンティティー・マネージャー・ファクトリーを作成する際に、次の例に示すように、AUTO、CALLBACK、または NONE の値で 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 Validation 仕様で記述されているように、属性レベルの制約違反の使用またはカスタムの制約違反の使用が提供される場合があります。サンプル
このトピックで提供されている JPA モデルとイメージ・ギャラリー・アプリケーションの使用法シナリオは、ホワイト・ペーパーの「OpenJPA Bean Validation Primer」で提供されているサンプルによって実装することができます。