Spring Bootでバリデーションチェックを実装する方法

アソビュー Advent Calendar 2019 の11日目の記事です。

こんにちは、@seitです。
Spring Bootでバリデーションチェック実装を試したので備忘録です。

github.com

アノテーションを使ったバリデーションチェックの方法

今回はSpring Frameworkで用意されている@Validatedアノテーション(org.springframework.validation.annotation)を使って、画面に入力された値のチェックを行います。

まずは簡単なFormとController、HTMLを用意し、text1フィールドを入力必須にしてみます。
テンプレートエンジンはThymeleafを使っています。

InputForm.java

public class InputForm {

    @NotBlank(message = "text1を入力してください")・・・①
    String text1;

    public String getText1() {
        return text1;
    }

    public void setText1(String text1) {
        this.text1 = text1;
    }
}

SampleController.java

@Controller
public class SampleController {

    @GetMapping("/index")
    public String index( InputForm inputForm){
        return "sample/index";
    }

    @PostMapping("/input")
    public String input(@Validated InputForm inputForm, BindingResult error, Model model){・・・②
        if(error.hasErrors()){・・・③
            return "sample/index";
        }

        return "sample/result";
    }
}

index.html

<!DOCTYPE html>
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head></head>

<body th:object="${inputForm}">

    <ul>・・・④
        <li th:each="error : ${#fields.detailedErrors()}">
            <span th:text="${error.message}">Error message</span>
        </li>
    </ul>

    <form  method="post" th:action="@{/input}" >
        <p>text1</p>
        <input path="text1" type="text" th:field="*{text1}"/>

        <input type="submit" value="送る"/>
    </form>
</body>

今回はtext1を必須入力にしたいので、そのフィールドに@NotBlankアノテーションを付与します(①)。
@NotBlankアノテーションは、指定したフィールドに空白が入力されることを許容しません。
独自のエラーメッセージを定義する場合は、message属性を使用します(message属性を定義しない場合はデフォルトのメッセージが返されます)。

Controllerが受け取るFormに@Validatedアノテーションを付与します(②)。
@Validatedを付与したFormに対して、SpringはController#inputハンドラが呼び出される前にバリデーションチェックを行います。
これだけで、text1に何も入力しない場合に、Controller#inputハンドラの引数BindingResultにエラーが渡されます。

Controllerでは、バリデーションエラーの発生有無を③でチェックしています。
Thymeleafでエラーを表示するには、#fieldsからエラー情報を取得することで表示できます(④)。

groupでチェック対象を絞る

バリデーションチェックは、@Validアノテーション(javax.validation)でも行うことができますが、@Validated(org.springframework.validation.annotation)を使うことでgroups属性を利用できます。
groups属性によって、状況に応じてチェック対象としたり、チェック対象から外したりといった制御が可能です。

SampleController.java

    @PostMapping("/input")
    public String input(@Validated(AlphaGroup.class) InputForm inputForm, BindingResult error, Model model){・・・①
        if(error.hasErrors()){
            return "sample/index";
        }

        return "sample/result";
    }

InputForm.java

    @NotBlank(groups = {AlphaGroup.class}, message = "text1を入力してください")・・・②
    String text1;

    @NotBlank(groups = {BetaGroup.class}, message = "text2を入力してください")・・・③
    String text2;

AlphaGroup.java

public interface AlphaGroup {・・・④
}

BetaGroup.java

public interface BetaGroup {・・・⑤
}

index.html

    <form  method="post" th:action="@{/input}" >
        <p>text1</p>
        <input path="text1" type="text" th:field="*{text1}"/>
        <p>text2</p>
        <input path="text2" type="text" th:field="*{text2}"/>

        <input type="submit" value="送る"/>
    </form>

今回は2つのGroup(AlphaGroup④、BetaGroup⑤)を用意しました。
InputFormのtext1をAlphaGroupに指定しています(②)。
InputFormにtext2を追加し、BetaGroupを指定しています(③)。
これで、@ValidatedにAlphaGroupを指定(①)することで、同じAlphaGroupに属するフィールド(text1)のみバリデーションチェックが入ります。

AssertTrueを用いた相関チェック

AssertTrueを用いることで、相関チェックを行うことができます。
例えば、Aが入力されている場合は、Bも必須入力とする、といったようなことができます。

今回の例では、text1が入力されている場合、text2の入力チェックを行います。
InputForm.java

    @NotBlank(groups = {AlphaGroup.class}, message = "text1を入力してください")
    String text1;
    
    String text2;

    // 相関チェック
    @AssertTrue(groups= {AlphaGroup.class},message = "text2を入力してください")
    public boolean isValid(){
        if(text1.isEmpty()) {
            // text1のバリデーションチェックはNotBlankに任せる
           return true;
        }

        // text1が入力されている時はtext2もチェックする
        if(text2.isEmpty()){
            // 未入力の場合はエラーとする 
            return false;
        }

        return true;
    }

今回は以上です。