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 アーキテクチャー内でどのように使用されているかが示されています。

最初のコード例では、image という名前の JPA モデルの単純なエンティティーに組み込み制約が追加されています。 Image には、ID、イメージのタイプ、ファイル名、およびイメージ・データがあります。 イメージのタイプを指定する必要があり、またイメージ・ファイル名に有効な JPEG 拡張子または GIF 拡張子が付いている必要があります。 このコードは、いくつかの組み込みの Bean Validation 制約が適用されている、アノテーション付き Image エンティティーを示しています。
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 属性内での変数の使用の規定が設けられています。 変数では、リソース・バンドル内のキー付きメッセージを参照します。 リソース・バンドルでは、環境固有のメッセージ、ならびにメッセージのグローバリゼーション、変換、および多文化がサポートされます。

ユーザー独自のカスタムのバリデーターと制約を作成することができます。 前の例では、Image エンティティーは、@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;
    }
}
この新規制約を 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 では、検証グループを使用して、どのタイプの検証をいつ実行するのかを判別します。

妥当性検査グループを作成するための、実装すべき特別なインターフェースや適用すべきアノテーションはありません。 妥当性検査グループは、クラス定義によって示されます。
ベスト・プラクティス ベスト・プラクティス: グループを使用する際は、分かりやすいインターフェースを使用してください。 単純なインターフェースを使用すると、妥当性検査グループを複数の環境でより有効に使用できます。 一方、クラス定義またはエンティティー定義を妥当性検査グループとして使用する場合、アプリケーションにとって意味をなさないドメイン・クラスとロジックを持ち込むことによって、別のアプリケーションのオブジェクト・モデルに悪影響を与える場合があります。 デフォルトでは、妥当性検査グループまたは複数のグループが個々の制約に指定されない場合、javax.validation.groups.Default グループを使用して妥当性検査が行われます。 カスタム・グループの作成は、インターフェース定義の新規作成と同じくらいに簡単です。bprac

OpenJPA の妥当性検査グループについて詳しくは、ホワイト・ペーパー「OpenJPA Bean Validation Primer」の『Validation groups』セクションを参照してください。

JPA ドメイン・モデル

Image エンティティーに加えて、Album、Creator、および Location のパーシスタント・タイプがあります。 Album エンティティーには、その Image エンティティーのコレクションへの参照が含まれています。 Creator エンティティーには、イメージ Creator が提供した Album エンティティーへの参照、および作成された Image エンティティーへの参照が含まれています。 これにより、ドメイン内の各エンティティー相互の、すべてのナビゲーション機能が提供されます。 Image と共にロケーション情報を保管するための機能をサポートするために、組み込み可能な Location が Image に追加されています。

Album エンティティーと Creator エンティティーには、標準の組み込みの制約が備わっています。 組み込みオブジェクトを妥当性検査するために @Valid アノテーションの使用が例示されている点で、組み込み可能な 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 ファイルを使用する必要があります。

バージョン 1.0 の persistence.xml では、Bean Validation を構成する手段は提供されていません。 バージョン 2.0 以降の persistence.xml を要求すれば、純粋な JPA 1.0 アプリケーションで妥当性検査の開始と実行時のコストが発生しなくなります。これは、1.0 ベースのアプリケーションで妥当性検査を使用不可にする標準の手段がない場合には重要です。 Java EE 環境で、既存の 1.0 アプリケーションで検証を使用可能にするには、persistence.xml ファイルのルート・エレメントに変更を行います。 以下に 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 では、JPA 環境内で以下の 3 つの操作モードが提供されます。
  • 自動

    妥当性検査プロバイダーがクラスパス内で使用可能な場合、Bean Validation が使用可能になります。 「AUTO」 (自動) がデフォルトです。

  • コールバック

    CALLBACK (コールバック) モードを指定する場合、Bean Validation プロバイダーが JPA プロバイダーで使用可能になっている必要があります。 妥当性検査プロバイダーが使用可能になっていない場合、新規 JPA エンティティー・マネージャー・ファクトリーのインスタンス化時に、JPA プロバイダーにより例外がスローされます。

  • なし

    Bean Validation を特定のパーシスタンス・ユニットに対して使用不可にします。

AUTO モードでは、デプロイメントが簡素化されますが、妥当性検査が構成の問題で実行されない場合、問題を引き起こす可能性があります。
ベスト・プラクティス ベスト・プラクティス: 安定した動作を実現するには、「なし」モードまたは「コールバック」モードを明示的に使用します。bprac
また、NONE を指定した場合、JPA では開始時に最適化して、予期しない妥当性検査の実行を試みません。 検証プロバイダーを提供するようにコンテナーに指示が出ている Java EE 環境では、検証を明示的に使用不可にすることが特に重要です。したがって、NONE を指定しない場合、コンテナー内で開始された JPA 2.0 以降のアプリケーションでは、妥当性検査が使用可能になります。このプロセスにより、ライフサイクル・イベント中に追加の処理が発生します。
JPA で妥当性検査のモードを構成するには、2 つの方法があります。最も簡単な方法は、次の例で示すように、validation-mode エレメントを必要な妥当性検査のモードで persistence.xml に追加することです。
 <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);
JPA 内での Bean Validation は、JPA ライフサイクル・イベント処理の間に実行されます。 この妥当性検査を使用可能にすると、妥当性検査は PrePersist、PreUpdate、および PreRemove の各ライフサイクル・イベントの最終段階で実行されます。 妥当性検査は、すべてのユーザー定義のライフサイクル・イベントの後でのみ実行されます。これは、それらのイベントのいくつかで妥当性検査されているエンティティーを変更できるためです。 デフォルトでは、JPA により、PrePersist および PreUpdate の各ライフサイクル・イベントの Default の妥当性検査グループに対する妥当性検査が使用可能にされます。 他の検証グループの検証を実行したり、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 ライフサイクルのすべての部分で発生する可能性があります。

1 つ以上の制約でライフサイクル・イベント中に妥当性検査できなかった場合、ConstraintViolationException が JPA プロバイダーによってスローされます。 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 Validation 仕様で記述されているように、属性レベルの制約違反の使用またはカスタムの制約違反の使用が提供される場合があります。

サンプル

このトピックで提供されている 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