takeda_san’s blog

JavaFXと機械学習を頑張る方向。

Spring MVC + Thymeleafでバリデーションしたい

いつも書き方を忘れて、ネットの海をさ迷っちゃうのでいい加減自分でまとめようと思う。

コードはここです。
Kotlinなのは特に意味はないけれど、定期的に書かないと忘れちゃうからです。

github.com

とりあえず入力チェック編

まずは、必須入力チェックをしてみる。

f:id:takeda_san:20180115231808p:plain

ポイントその1:@NotNullじゃなくて@NotBlankを使うよ
@NotBlank
var name: String = ""

ここ、HTMLのtextフォームからフォームクラスにStringで値が入るときには、空""で入ってくる仕様らしい。
なのでnullかどうかを見る、@NotNullだとバリデートエラーにならなくて、空白か空かどうかも見てくれる、@NotBlankのほうがよさそう。

ポイントその2:BindingResultは必ず@Validatedの後
@RequestMapping(method = arrayOf(RequestMethod.POST))
fun doPost(@Validated inputForm: InputForm, bindingResult: BindingResult, model: Model): String {

    return "input/form";
}

BindingResultには、@Validatedを付けた引数のバリデート結果が入る。
んで、このBindingResult@Validatedが付いた引数の後ろに定義するという村の決まりごとがあって、これをしないとエラーでこける。

日本語で表示したい編

これ、エラー表示が英語じゃないですか!
そうですね、日本語で表示しましょう。

resourcesディレクトリの直下にValidationMessages.propertiesというファイルを作って、以下を書く。

org.hibernate.validator.constraints.NotBlank.message={0}:必須入力ですよ

んで、もう一度アプリケーション起動して、送信してみる。

f:id:takeda_san:20180116223249p:plain org.springframework.context.support.DefaultMessageSourceResolvable: codes [inputForm.name,name]; arguments []; default message [name]:必須入力ですよ

なんかtoStringっぽいのが出ているが、無事指定したメッセージが出でる。 @NotBlankは、hibernate validatorのアノテーションなので、そのデフォルトのメッセージ定義を上書きしてあげればよい。
ほかのアノテーションの定義は↓から、確認できる。

github.com

ポイントその3 プロパティファイルのはエンコーディングUTF-8

IDEとかで、プロパティファイルを作ると ISO-8859-1でエンコードされたりするんだけど、これだと日本語が文字化けしてうまく出力されない。
ちゃんと、エンコードの設定を確認する。

ポイントその4 {0}を使うときはメッセージ定義ファイルを作る

で、ここ({0})の部分。
ほかの解説ページとかだと、プロパティ名(今回の例だとname)が出るって書いてあるんだけど、うまく出てない。
org.springframework.context.support.DefaultMessageSourceResolvable: codes [inputForm.name,name]; arguments []; default message [name]

なぜかはわからんが、メッセージ定義ファイルを作ってあげるとちゃんと出る。
(しばらくなんでか調べたけどよくわからんかった)

外部にメッセージ定義(リソース?)を持つ設定の仕方は、いろんなところに書いてあると思うので説明は割愛。
application.propertiesに以下を設定。

spring.messages.basename=messages
spring.messages.cache-seconds=-1
spring.messages.encoding=UTF-8

resourcesディレクトリの直下にmessages.propertiesを作る。
(ファイルの名前は設定と一致してればなんでもよさそう)

もう一度起動しなおして、送信。
f:id:takeda_san:20180116224841p:plain

プロパティ名が出ました。
エラーメッセージにプロパティ名が出たのはいいけど、ユーザからしたらnameって何ぞや状態なので、これも差し替えましょう。
さっき作ったmessages.propertiesに、↓を書く。

name=名前

すると、ちゃんとnameのところが差し変わっている。
f:id:takeda_san:20180116225135p:plain

ちなみに、nameの部分はプロパティ名で、inputForm.nameとかでもちゃんと置換される。

独自のメッセージを表示したい編

おなじ、@NotBlankエラーでも違うエラーメッセージを項目ごとに出したいことがある。
その時は、次のようにアノテーションを指定する。

InputForm.kt

@NotBlank(message="年齢も恥ずかしがらずにちゃんと入力しよう")
var age: String = ""

アノテーションに直接メッセージを埋め込みたくない、几帳面なあなたはValidationMessages.propertiesにメッセージを書いて、それのキーを埋め込もう。

InputForm.kt

@NotBlank(message="{E0001}")
var age: String = ""

ValidationMessages.properties

E0001=年齢も恥ずかしがらずにちゃんと入力しよう

E0001、業務感がすごくてわくわくする

f:id:takeda_san:20180116230339p:plain

相関チェックしたい編

独自のチェックだったり、二つ以上の項目についてチェックしたいことがある。
そんな時は、自分でアノテーションを定義して、それを使う。

長々と書いてあるが、isValidがfalseならエラーになる。
それだけ。

    public boolean isValid(Object value, ConstraintValidatorContext context) {
        BeanWrapper beanWrapper = new BeanWrapperImpl(value);

        String param1 = (String)beanWrapper.getPropertyValue(this.param1Field);
        String param2 = (String)beanWrapper.getPropertyValue(this.param2Field);

        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(message)
                .addPropertyNode(this.param2Field)
                .addConstraintViolation();

        return Objects.equals(param1, param2);
    }

なぜここだけJavaなのか…
それはKotlinでアノテーションの書き方がわからなかったからだよ。
悲しい。

ポイントその5 アノテーションは、クラス宣言の上につける

相関チェックみたいに複数のプロパティを対象とするバリデートのアノテーションは、クラスの宣言部分の上につける。

@Equals(param1 = "email1", param2 = "email2")
class InputForm {

こんな感じ。

f:id:takeda_san:20180118223931p:plain