7
8

More than 3 years have passed since last update.

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

Posted at

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

前回の記事🌟
【Java・SpringBoot・Thymeleaf】ログイン・新規登録画面作成(SpringBootアプリケーション実践編1)

データバインドとは?

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

Springでのデータバインド

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

データバインド実践!

構成は以下のようになっています

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にリダイレクト(ユーザーの登録はしない)
    =ユーザー登録画面から、ユーザー登録用フォームクラスへデータバインドしているということです

ユーザー登録画面用のフォームクラスを作成

  • データバインド用のアノテーション
    • @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) {...}
  • つまり、以下のコードイメージ
//イメージはこう
@GetMapping("/signup")
public String getSignUp(SignupForm form, Model model){
    //フォームクラスをModelに登録 
    model.addAttribute("SignupForm",form);
    //login.htmlに画面遷移
    return"login/signup";
}

データバインド結果を受け取る

  • メソッドの引数にBindingResultクラスを追加
    • hasErros()メソッドで、データバインドに失敗しているかどうかが分かる
    • バリデーションエラーが発生した場合も、失敗しているかどうかが分かる
        // 入力チェックに引っかかった場合、ユーザー登録画面に戻る
        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を書かなくても値の取得・送信ができる
<!-- コード修正例 -->
<inputtype="text"th:field="${signupForm.userId}"/>
  • ②th:objectが付いたタグ内であれば、オブジェクト名を省略可能
    • th:field="∗{<フィールド名>}"
    • 画面から送るフィールドが多いときに有効

エラーメッセージをまとめて一覧表示

  • th:each属性:拡張for文のようにModelに登録されている値が繰り返し呼ばれる
    • th:each="<変数名>:${<ModelAttributeのキー名>}"
<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

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

バインド失敗時

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

error.png

7
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8