エンタープライズギークス (Enterprise Geeks)

企業システムの企画・開発に携わる技術者集団のブログです。開発言語やフレームワークなどアプリケーション開発に関する各種情報を発信しています。ウルシステムズのエンジニア有志が運営しています。

JavaEE7をはじめよう(25) - Bean Validationで複雑な条件を記述する

前々回前回の記事では、Bean Validation の基本的なアノテーションの使用方法と、カスタムアノテーションの作成方法を紹介した。基本的にこれらは、1つの Bean に対して共通のバリデーションを定義するものである。

今回は、1つの Bean に対して複数の制約を定義して、バリデーションの実行時に柔軟に切り替える方法を紹介する。

Listを使って同じ名前の制約を複数定義する

制約アノテーションは内部クラスとしてListアノテーションを持っている。 これは、同じ制約を異なる条件で複数定義したい時に使用する。 (同一のアノテーション複数指定することはできないので、それを回避するための方法である。)

前回紹介したバイト長をチェックする例で、文字コードによって異なる規定バイト数をチェックしたい場合は、次のように記述できる。

public class Hoge {
    // UTF-8では15バイト以内、Windows-31Jでは10バイト以内
    @Byte.List({
        @Byte(charset = "UTF-8",  max=15),
        @Byte(charset = "Windows-31J",  max=10),
    })
    private String name;
}

このようにすると、両方の制約がチェックされる。どちらかの制約に引っかかると、バリデーションは失敗する。

groups属性を使って制約をグルーピングする

制約アノテーションgroups属性を指定すると、制約を任意のグループにまとめることができる。バリデーションの実行時にそのグループを指定することで、グループごとに異なる制約のチェックが行える。

グループの定義

groups属性には、classオブジェクトの配列を指定する。指定できるのはインターフェースのみで、クラスは指定できない。

このインターフェースはグループの種類を表し、列挙型のような使い方をするため、通常はグルーピング専用のインターフェースを定義する。

たとえば、以下のようなインターフェースを定義する。

public interface GruopA {}

制約は以下のように記述する。

public class Hoge {
    
    // Defaultグループ → @NotNull,  GroupA → @Size
    @NotNull
    @Size(min = 2, groups = {GroupA.class})
    private String name;
    
    // Defaultグループ → @NotNullと@Max, GroupA → @Max
    @NotNull
    @Max(value = 200, groups = {Default.class, GroupA.class})
    private Integer age;
}

groupsを指定していない場合は、javax.validation.groups.Defaultを暗黙的に指定したものとみなされる。

上記のように定義した場合、次のように解釈される。

  • バリデーション実行時にDefaultグループ を指定した場合
    nameには@NotNullを、ageには@NotNull@Maxを適用する。
  • バリデーション実行時にGroupAグループを指定した場合
    nameには@Sizeを、ageには@Maxを適用する。

このように、制約アノテーションにグループを指定することで、そのグループに応じた制約をチェックできる。

グループに対するバリデーションの実行

javax.validation.Validatorvalidate等のメソッドは可変長引数でグループを複数指定できるため、Validator#validateメソッドの呼び出し方法は以下のようになる。(グループを指定しない場合は、Defaultを暗黙的に指定したものと解釈する。)

  • Defaultグループを指定してバリデーションを行う場合
    validator.validate(bean)またはvalidator.validate(bean, Default.class)
  • GroupAグループを指定してバリデーションを行う場合
    validator.validate(bean, GroupA.class)
  • 両グループを指定してバリデーションを行う場合
    validator.validate(bean, Default.class, GroupA.class)

ただし、Bean Validationを組み込むフレームワークによってはグループを指定できないものもあるので、事前に確認しておく必要がある。

Listgroupsを組み合わせる

Listgroupsを組み合わせると、グループの性質に合わせた制約条件を記述できる。

例えば以下のように、グループに応じて実数型の小数の有効桁数を制御するような定義が可能だ。

public class Rate {

    // Nullはどの場合でも許可しない
    @NotNull(groups = {Default.class, USD.class)
    @Digits.List(
      // デフォルトは小数なし。USDの場合は小数二桁
      @Digits(integer = 5, fraction = 0/*, groups=Default.class*/)
      @Digits(integer = 5, fraction = 2, groups = USD.class)
    )
    private BigDecimal rate;
}

強力な機能である。ただし、どのようなグループ分けを行うか、またどこまでを Bean Validation で実装し、どこからチェックロジックを作りこむかについて、アプリケーション全体で方針を統一しておかないと、混乱の元になるだろう。

まとめ

Bean Validation のListgroupsの使用例を紹介した。Listを単体で使うケースはあまりなく、groupsと組み合わせて使うことが多いだろう。groupsを使うと、状況に応じてチェック内容を柔軟に変更できる。

例えば、JPA のエンティティについて、新規登録・更新時は全てのフィールドのバリデーションを行い、削除時は ID 属性のみをバリデーションするといったケースに適用できる。

次回は、Listgroupsを利用した JPA のバリデーションの仕組みを紹介する。

[前多 賢太郎]