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.Validator
のvalidate
等のメソッドは可変長引数でグループを複数指定できるため、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を組み込むフレームワークによってはグループを指定できないものもあるので、事前に確認しておく必要がある。
List
とgroups
を組み合わせる
List
とgroups
を組み合わせると、グループの性質に合わせた制約条件を記述できる。
例えば以下のように、グループに応じて実数型の小数の有効桁数を制御するような定義が可能だ。
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 のList
とgroups
の使用例を紹介した。List
を単体で使うケースはあまりなく、groups
と組み合わせて使うことが多いだろう。groups
を使うと、状況に応じてチェック内容を柔軟に変更できる。
例えば、JPA のエンティティについて、新規登録・更新時は全てのフィールドのバリデーションを行い、削除時は ID 属性のみをバリデーションするといったケースに適用できる。
次回は、List
とgroups
を利用した JPA のバリデーションの仕組みを紹介する。
[前多 賢太郎]