【Java・SpringBoot・Thymeleaf】バリデーションチェックの順番を設定（SpringBootアプリケーション実践編7）


ログインをして、ユーザー一覧を表示するアプリケーションを作成し、
Springでの開発について勉強していきます🌟
前回は入力項目毎にエラーメッセージを表示し、バリデーションのチェックを全て同時に実行しました。
今回は**バリデーションチェックの順番を設定**します^^

前回の記事🌟
[【Java・SpringBoot・Thymeleaf】エラーメッセージを個別に表示（SpringBootアプリケーション実践編6）](https://qiita.com/suema0331/items/9b138e5aa2b46009694e)

#入力チェックのグループを設定
* 入力チェックに引っかかった場合に処理を途中で止めることができる！
* バリデーションでグループを作成するために**インターフェースを作成**
* 構成は以下のようになっています

```
Project Root
└─src
    └─ main
        └─ java  
            └─ com.example.demo
                └─ login
                    └─ controller                ...コントローラクラス用パッケージ
                └─ domain                        ...ビジネスロジック用パッケージ
                    └─ model                     ...Modelクラス用パッケージ
                        └─ LoginForm.java
                        └─ SignupForm.java
                        └─ ValidGroup1.java
                        └─ ValidGroup2.java
                        └─ ValidGroup3.java
                        └─ GroupOrder.java

```

##インターフェースの用意
* 中身は空のインターフェースを作ります
    * ValidGroup2,3も同様
    * IntelliJはコピーペーストでいい感じに修正してくれます

```ValidGroup1.java
package com.example.demo.login.domain.model;

public interface ValidGroup1 {

}
```

##グループ実行順を決めるインターフェースを用意
* 実行順を設定するインターフェースに**@GroupSequenceアノテーション**をつけ、パラメータに各グループの.classを指定
* **指定順にバリデーションが実行**される

```GroupOrder.java
package com.example.demo.login.domain.model;

import javax.validation.GroupSequence;

@GroupSequence({ ValidGroup1.class, ValidGroup2.class, ValidGroup3.class })
public interface GroupOrder {

}

```

##コントローラークラスを編集
* @Validatedアノテーションのパラメーターに、**実行順序のインターフェースを指定**し、バリデーションをグループ実行
* グループのインターフェースを**直接指定することも可能**（ValidGroup1.classなど）
    * コードの全文は下記参考

```SignupController.java
public String postSignUp(@ModelAttribute @Validated(GroupOrder.class) SignupForm form,
                             BindingResult bindingResult,
                             Model model) {...}
```

##各フィールドをどのグループにするか指定
* groups属性にインターフェースのクラスを指定し、フィールドとグループの紐付けができる
    * コードの全文は下記参考

```SignupForm.java
@NotBlank(groups = ValidGroup1.class)
```

#ユーザー登録画面確認！
* http://localhost:8080/signup
* ユーザー登録画面で何も入力せずユーザー登録ボタンを押すと、エラーメッセージが表示される
    * **まずは必須入力チェック**が行われた
* 必須入力を入力したら、次に中身のチェックを行う
* **エラーメッセージを順番に実行**することができました〜〜o(^^)o


![エラー2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/74057391-3130-b238-2f14-780420366b22.png)

![エラー1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/2fe658a5-9bcc-6dff-e53d-f3af3d61b34f.png)





#(参考)コード全文

```SignupController.java
package com.example.demo.login.controller;

import java.util.LinkedHashMap;
import java.util.Map;

import com.example.demo.login.domain.model.GroupOrder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.login.domain.model.SignupForm;

@Controller
public class SignupController {

    //ラジオボタン用変数
    private Map<String, String> radioMarriage;

    //ラジオボタンの初期化メソッド
    private Map<String, String> initRadioMarrige() {
        Map<String, String> radio = new LinkedHashMap<>();
        // 既婚、未婚をMapに格納
        radio.put("既婚", "true");
        radio.put("未婚", "false");
        return radio;
    }

    //ユーザー登録画面のGET用コントローラ
    @GetMapping("/signup")
    public String getSignUp(@ModelAttribute SignupForm form, Model model) {

        // ラジオボタンの初期化メソッド呼び出し
        radioMarriage = initRadioMarrige();
        // ラジオボタン用のMapをModelに登録
        model.addAttribute("radioMarriage", radioMarriage);
        // signup.htmlに画面遷移
        return "login/signup";
    }

    //ユーザー登録画面のPOST用コントローラ
    //バリデーション実装
    @PostMapping("/signup")
    public String postSignUp(@ModelAttribute @Validated(GroupOrder.class) SignupForm form,
                             BindingResult bindingResult,
                             Model model) {

        // 入力チェックに引っかかった場合、ユーザー登録画面に戻る
        if (bindingResult.hasErrors()) {
            // GETリクエスト用のメソッドを呼び出して、ユーザー登録画面に戻る
            return getSignUp(form, model);
        }

        // formの中身をコンソールで確認
        System.out.println(form);
        // login.htmlにリダイレクト
        return "redirect:/login";
    }
}
```

```SignupForm.java
package com.example.demo.login.domain.model;

import java.util.Date;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;

import lombok.Data;

@Data
public class SignupForm {

    //必須入力、メールアドレス形式
    @NotBlank(groups = ValidGroup1.class)
    @Email(groups = ValidGroup2.class)
    private String userId; // ユーザーID

    //必須入力、長さ4から100桁まで、半角英数字のみ
    @NotBlank(groups = ValidGroup1.class)
    @Length(groups = ValidGroup2.class)
    @Pattern(regexp = "^[a-zA-Z0-9]+$", groups = ValidGroup3.class)
    private String password; // パスワード

    //必須入力
    @NotBlank(groups = ValidGroup1.class)
    private String userName; // ユーザー名

    //必須入力
    @NotNull(groups = ValidGroup1.class)
    @DateTimeFormat(pattern = "yyyy/MM/dd")
    private Date birthday; // 誕生日

    //値が20から100まで
    @Min(value = 20, groups = ValidGroup2.class)
    @Max(value = 100, groups = ValidGroup2.class)
    private int age; // 年齢

    //falseのみ可能
    @AssertFalse(groups = ValidGroup2.class)
    private boolean marriage; // 結婚ステータス
}

```