怠惰系エンジニアのメモ帳

勉強した内容をメモしていきます。解説ブログではないので悪しからず。

BeanValidationメモ_その1

Bean Validationを利用した入力チェックについて調べる機会があったので、備忘を兼ねてメモ。
特に目新しいことは記載していません。

記載内容に間違いなどありましたら、指摘していただけると幸いです。

参考

以下のサイト、書籍を参考にさせていただきました。

環境

今回もSpring Boot。 Bean Validationを利用したいので spring-boot-starter-validation を依存関係に追加する。
spring-boot-starter-validation を追加することで、Bean Validationの参照実装である Hibernate Validator が利用できるようになる。

Hibernate Validator のバージョンは、5.3.5.Final。

使い方

チェックを行いたいフィールドやメソッド、パラメータに対して 制約アノテーション を注釈して、検証を実行する。 ※ SpringMVCと連携する場合は、検証の実行はSpringが行う。

@NotEmpty  // name が null または 空文字 であることを許容しない
private String name;

@NotEmpty などの、チェックルール(制約)を定義したアノテーション制約アノテーション と呼んでいるよう。(ニュアンス的には合っているはず…)
@NotEmpty 以外に提供されている制約アノテーションとしては、

  • @Size:指定された範囲に収まっているか
  • @NotNull:nullでないか
  • @Min:指定された値以上であるか
  • @Max:指定された値以下であるか

などがある。

入力チェックを実行してみる

利用する機会が一番多そうな、フィールドに対する入力チェックを想定。
汎用的なフィールドを持つBeanを定義する。※今回は、文字列と整数、日付の3つ。

@Data // Lombokのアノテーション
public class GeneralBean {
    
    @NotEmpty // null、または空文字を許可しない
    private String stringField;

    @Range(min = 0, max = 100) // 0〜100の範囲であること
    private Integer intField;

    @Past // 過去日であること
   private Date dateField;

}

上記、 GeneralBean に対してチェックを行う。

public class BeanValidationTest {

    @Test
    public void test_defaultBeanValidation() {

        // 検証に失敗するように値を設定する。
        GeneralBean generalBean = new GeneralBean();
        generalBean.setStringField("");
        generalBean.setIntField(120);
        generalBean.setDateField(Date.valueOf(LocalDate.now().plusDays(1)));

        validate(generalBean);  // 検証を実行するメソッド
    }

    /**
     *  チェックを実施するメソッド
     */
    public <T> void validate(T target) {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Set<ConstraintViolation<T>> result = validator.validate(target);  // チェック実行

        result.stream()
            .map(ConstraintViolation::getMessage)
            .forEach(System.out::println);
    }
}

実行結果

may not be empty
must be between 0 and 100
must be in the past

Spring MVCと連携して入力チェックを行う場合、 以下のように検証対象のパラメータに @Validated を注釈して、直後に BindingResult を受け取るようにする。

@GetMapping("/")
public String handlerMethod(@Validated GenerateBean bean, BindingResult result) {

ネストしたクラスに対するチェック

検証対象のフィールドがネストしたクラスの場合、 @Validアノテーションを注釈すれば、ネストしたクラスも検証対象に含めてくれる。

public class BeanA {
    @NotEmpty
    private String field;
}
public class BeanB {
    // ネストしたクラスに対してバリデーションを行う場合には、
    // @Validアノテーションを注釈する。
    @Valid
    private BeanA beanA;
}

デフォルトメッセージを変更する

チェクエラー時のデフォルトメッセージはHibernate Validatorが提供しているもので、メッセージは英語。
※日本語にも対応してくれていれば良かったのですが、どうも対応していないよう。 Spring Boot(+Spring MVC)と連携した場合、デフォルトメッセージを変更する方法はいくつかあるみたいだが、 個人的に一番楽だと感じたのは クラスパス上に ValidationMessages.properties を作成する方法。

今回はSpring Bootを利用しているので、main¥resources 配下に ValidationMessages.properties を作成。
※注意点として、作成するメッセージはnative2asciiエンコードしないと文字化けした…。

メッセージは、キー=メッセージ の形式で定義していく。
キーは、メッセージを変更したい制約アノテーションmessage 属性のデフォルト値と同じにする必要がある。
例えば、@Range 制約アノテーション

@Documented
@Constraint(validatedBy = { })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Min(0)
@Max(Long.MAX_VALUE)
@ReportAsSingleViolation
public @interface Range {
    @OverridesAttribute(constraint = Min.class, name = "value") long min() default 0;

    @OverridesAttribute(constraint = Max.class, name = "value") long max() default Long.MAX_VALUE;

    String message() default "{org.hibernate.validator.constraints.Range.message}";

// 以下略

と定義されており、message 属性のデフォルト値は org.hibernate.validator.constraints.Range.message となっている。
このデフォルト値をキーとするので

# @Rangeのデフォルトメッセージを上書き
# [ValidationMessages.properties]{min}以上、{max}以下の値を入力してください!
org.hibernate.validator.constraints.Range.message = [ValidationMessages.properties]{min}\u4ee5\u4e0a\u3001{max}\u4ee5\u4e0b\u306e\u5024\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\uff01

という感じで、変更したいメッセージを定義する。 ※message属性のデフォルト値=上書きするメッセージテンプレート かな?。

本当にデフォルトメッセージが変更されるのか確認するために、再度 GeneralBean に対してバリデーションを行ってみる。
※コードは上記同様。

結果

may not be empty
[ValidationMessages.properties]0以上、100以下の値を入力してください!
must be in the past

メッセージが変更されていることが確認できた!!

まとめ

  • 入力チェックしたいフィールドに制約アノテーションを注釈する
  • ネストしたクラスの場合は、@Valid アノテーション
  • ValidationMessages.properties ファイルを作成し、制約アノテーションのmessage属性のデフォルト値=上書きするメッセージテンプレート を定義することでメッセージの上書きができる。

次回

カスタムバリデーションについてメモする。(検証済み!!)