【Java・SpringBoot・Thymeleaf】データバインド（SpringBootアプリケーション実践編2）

ログインをして、ユーザー一覧を表示するアプリケーションを作成し、
Springでの開発について勉強していきます🌟
前回の記事で作った画面に引き続き、データバインドを実装します

前回の記事🌟
[【Java・SpringBoot・Thymeleaf】ログイン・新規登録画面作成（SpringBootアプリケーション実践編1）]
(https://qiita.com/suema0331/items/5e69a90a2f18ea3b5b67)

#データバインドとは?
* 画面の入力項目とオブジェクトのフィールドの**マッピング（割り当て）**を行うこと
* 画面から渡された値を、**フィールドのデータ型に合わせて変換**してくれる

##Springでのデータバインド
* Springでは、データバインドをある程度自動で行ってくれるが
    * ex: 画面からテキストで入力した数値を、オブジェクトのint型に変換するなど
* データの型変換が難しい場合は、**アノテーションを使う**ことでバインドできます

#データバインド実践！
構成は以下のようになっています

```html
Project Root
└─src
    └─ main
        └─ java  
            └─ com.example.demo
                └─ login
                    └─ controller                ...コントローラクラス用パッケージ
                        └─ LoginController.java
                        └─ SignupController.java
                └─ domain                        ...ビジネスロジック用パッケージ
                    └─ model                     ...Modelクラス用パッケージ
                        └─ SignupForm.java
        └─ resouces
            └─ static                            ...css,js用フォルダ
            └─ templates
                └─ login
                    └─ login.html
                    └─ signup.html
```

##ユーザー登録処理の内容
* 1.ログイン画面からコントローラー（SignupController）にGETリクエストを送信
* 2.ユーザー登録画面に遷移、**ユーザー登録用フォームクラスのインスタンス（SignupForm）をユーザー登録画面に渡す**
* 3.ユーザー登録ボタンをクリックすると、**フォームクラスのインスタンスをコントローラークラスに渡す**
* 4.フォームクラスのインスタンスを受け取ったら`/login`にリダイレクト（ユーザーの登録はしない)<br>=**ユーザー登録画面から、ユーザー登録用フォームクラスへデータバインド**しているということです


##ユーザー登録画面用のフォームクラスを作成
* **データバインド用のアノテーション**
    * **@DateTimeFormat**：指定されたフォーマットの**文字列を日付型**に変換
        * 以下の例では画面から渡されてきた文字列を日付型に変換
        * pattern属性にどのようなフォーマットでデータが渡されてくるかを指定している
        * `@DateTimeFormat(pattern = "yyyy/MM/dd")`
    * **@NumberFormat**：指定されたフォーマットの**文字列を数値型**に変換
    

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

import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;

@Data
public class SignupForm {
    private String userId;
    private String userName;
    @DateTimeFormat(pattern = "yyyy/MM/dd")
    private Date birthday;

    private int age;
    private boolean marriage;

}
```

##コントローラークラスを編集し、フォームクラスを受け取る
###@ModelAttribute
* 引数のフォームクラスに@ModelAttributeアノテーションを付けると、自動でModelクラスに登録（addAttribute）してくれる！
    * `public String getSignUp(@ModelAttribute SignupForm form, Model model) {...}`
* つまり、以下のコードイメージ

``` java
//イメージはこう
@GetMapping("/signup")
public String getSignUp(SignupForm form, Model model){
    //フォームクラスをModelに登録 
    model.addAttribute("SignupForm",form);
    //login.htmlに画面遷移
    return"login/signup";
}
```
###データバインド結果を受け取る
* メソッドの引数に**BindingResultクラス**を追加
    * `hasErros()`メソッドで、データバインドに失敗しているかどうかが分かる
    * **バリデーションエラー**が発生した場合も、失敗しているかどうかが分かる

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

* データバインドに失敗した場合、**hasErrors()メソッドでfalse**が返る
    * 上のコードではデータバインドに失敗した場合、ユーザー登録画面に戻り、getSignUpメソッドを呼び出す
    * **→ラジオボタン用の変数を初期化**してくれる
   



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

import java.util.LinkedHashMap;
import java.util.Map;
import com.example.demo.login.domain.model.SignupForm;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@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 SignupForm form,
                             BindingResult bindingResult,
                             Model model) {
        // 入力チェックに引っかかった場合、ユーザー登録画面に戻る
        if (bindingResult.hasErrors()) {
            // GETリクエスト用のメソッドを呼び出して、ユーザー登録画面に戻る
            return getSignUp(form, model);
        }
        // formの中身をコンソールに出して確認
        System.out.println(form);
        // login.htmlにリダイレクト
        return "redirct:/login";
    }
}
```

##ユーザ登録用の画面
###th:object属性
* Modelに登録されているオブジェクトを受け取る
    * `th:object="${<ModelAttributeのキー名>}"`
    * 以下の例ではSignupFormクラスを受け取っている
        * `<form method="post" action="@{/signup}" th:object="${signupForm}">`
* th:objectを付けたタグの中では、**th:fieldでそのオブジェクト名を省略可能**

###th:fieldの使い方
* th:fieldを使用すると、**オブジェクトの中のフィールドを取得し、コントローラークラスに値を渡せる**
* ①th:object属性を書かなかった場合は
    * `th:field="${<ModelAttributeのキー名.フィールド名>}"`
* フィールドを1つしか使わない場合など、th:objectを書かなくても値の取得・送信ができる      

```html
<!-- コード修正例 -->
<inputtype="text"th:field="${signupForm.userId}"/>
```

* ②th:objectが付いたタグ内であれば、**オブジェクト名を省略可能**
    * `th:field="∗{<フィールド名>}"`
    * 画面から送るフィールドが多いときに有効

###エラーメッセージをまとめて一覧表示
* **th:each属性**：拡張for文のようにModelに登録されている値が繰り返し呼ばれる
    * `th:each="<変数名>:${<ModelAttributeのキー名>}"`

```html
<li th:each="error : ${#fields.detailedErrors()}">
     <span th:text="${error.message}">Error message</span>
</li>
```
* **これでデータバインド実装完成！**


```signup.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"></meta>

    <!-- Bootstrapの設定 -->
    <link th:href="@{/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css}" rel="stylesheet"></link>
    <script th:src="@{/webjars/jquery/1.11.1/jquery.min.js}"></script>
    <script th:src="@{/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js}"></script>

    <title>SignUp</title>
</head>
<body>
    <div class="col-sm-5">
        <div class="page-header">
            <h1>ユーザー登録画面</h1>
        </div>
        <form method="post" th:action="@{/signup}" th:object="${signupForm}">
            <table class="table table-bordered table-hover">
                <!-- ユーザーID -->
                <tr>
                    <th class="active col-sm-3">ユーザID</th>
                    <td>
                        <div class="form-group">
                            <input type="text" class="form-control" th:field="*{userId}" />
                        </div>
                    </td>
                </tr>
                <!-- パスワード -->
                <tr>
                    <th class="active">パスワード</th>
                    <td>
                        <div class="form-group">
                            <input type="text" class="form-control" th:field="*{password}" />
                        </div>
                    </td>
                </tr>
                <!-- ユーザー名 -->
                <tr>
                    <th class="active">ユーザー名</th>
                    <td>
                        <div class="form-group">
                            <input type="text" class="form-control"  th:field="*{userName}" />
                        </div>
                    </td>
                </tr>
                <!-- 誕生日 -->
                <tr>
                    <th class="active">誕生日</th>
                    <td>
                        <div class="form-group">
                            <input type="text" class="form-control" placeholder="yyyy/MM/dd" th:field="*{birthday}"/>
                        </div>
                    </td>
                </tr>
                <!-- 年齢 -->
                <tr>
                    <th class="active">年齢</th>
                    <td>
                        <div class="form-group">
                            <input type="text" class="form-control" th:field="*{age}" />
                        </div>
                    </td>
                </tr>
                <!-- 結婚 -->
                <tr>
                    <th class="active">結婚</th>
                    <td>
                        <div class="form-group">
                            <div th:each="item : ${radioMarriage}">
                                <input type="radio" name="radioMarrige"
                                       th:text="${item.key}"
                                       th:value="${item.value}"
                                       th:field="*{marriage}">
                                </input>
                            </div>
                        </div>
                    </td>
                </tr>
            </table>
            <!-- エラーメッセージの一覧表示 -->
            <ul>
                <li th:each="error : ${#fields.detailedErrors()}">
                    <span th:text="${error.message}">Error message</span>
                </li>
            </ul>

            <button class="btn btn-primary" type="submit">ユーザー登録</button>
        </form>
    </div>
</body>
</html>
```

#SpringBootを起動して、ログイン画面を確認！
* http://localhost:8080/login
* ユーザー登録ボタンをクリックするとログイン画面に遷移し、**コンソールにSignupFormクラスの中身が表示される！**
* **画面からフォームクラスに値を渡し、フォームクラスをコントローラークラスに渡すことができました**😊

![登録ok.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/a1c76c28-0867-a443-e0de-e33cfa1a0b8a.png)

```java
//コンソール
SignupForm(userId=NEKO, password=password, userName=neko, birthday=Tue Jan 23 00:00:00 JST 2018, age=2, marriage=false)
```

##バインド失敗時
* ２つのフィールドでバインドに失敗しているため、エラーメッセージが２つ表示されます
* が分かりにくい。。。
* 次はこのエラーメッセージを編集します^^


![error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282755/a21efb2d-e580-6ca6-9ad4-ee165ab04050.png)