Thymeleafのth:objectとth:fieldを使ったフォーム送信サンプル|新人エンジニア向けに解説

こんにちは。ゆうせいです。

今回は、次のThymeleafのフォームコードに、簡単なSpring Bootのサンプルコードと解説を追加します。

<form th:action="@{/update}" th:object="${user}" method="post">
    <input type="text" th:field="*{name}" />
    <input type="email" th:field="*{email}" />
    <button type="submit">送信</button>
</form>




このコードは、ユーザーの名前とメールアドレスを入力し、Spring BootのControllerへPOST送信するフォームです。

新人エンジニア向けに一言で言うと、th:objectは「このフォームで使うJavaオブジェクトを指定するもの」、th:fieldは「そのオブジェクトのどのプロパティと入力欄を結びつけるかを指定するもの」です。

たとえるなら、th:objectは記入する申込用紙そのもの、th:fieldは申込用紙の「名前欄」「メールアドレス欄」です。

全体の完成イメージ

今回作る流れは、次のとおりです。

順番処理役割
1ControllerでUserオブジェクトを作る画面に渡す入力用データを用意する
2Modelにuserという名前で入れるThymeleafから${user}で使えるようにする
3Thymeleafで入力フォームを表示するnameとemailを入力できるようにする
4送信ボタンを押す/updateへPOST送信する
5ControllerでUserとして受け取る入力された値をJavaオブジェクトとして扱う

Userクラス

まず、フォームの入力値を受け取るUserクラスを作ります。

今回は学習用なので、シンプルにnameとemailだけを持つクラスにします。

package com.example.demo.form;

public class User {

    private String name;
    private String email;

    public User() {
    }

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}




ここで重要なのは、getterとsetterです。

Thymeleafのth:field="*{name}"は、Java側のgetName()やsetName()と対応します。

同じように、th:field="*{email}"は、getEmail()やsetEmail()と対応します。

Thymeleaf対応するJavaのメソッド
*{name}getName()、setName()
*{email}getEmail()、setEmail()

もしgetName()やsetName()がないと、ThymeleafやSpring Bootがnameプロパティをうまく扱えないことがあります。

プロパティは、フィールドだけでなく、getterとsetterまでセットで考えましょう。

Controllerクラス

次に、画面を表示する処理と、フォーム送信後に値を受け取る処理を作ります。

package com.example.demo.controller;

import com.example.demo.form.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class UserController {

    @GetMapping("/edit")
    public String showEditForm(Model model) {

        User user = new User();
        user.setName("山崎太郎");
        user.setEmail("yamazaki@example.com");

        model.addAttribute("user", user);

        return "user/edit";
    }

    @PostMapping("/update")
    public String update(User user, Model model) {

        System.out.println("送信された名前: " + user.getName());
        System.out.println("送信されたメールアドレス: " + user.getEmail());

        model.addAttribute("user", user);
        model.addAttribute("message", "更新内容を受け取りました。");

        return "user/result";
    }
}




このControllerには、2つのメソッドがあります。

メソッドURL役割
showEditForm/edit入力フォーム画面を表示する
update/updateフォームから送信された値を受け取る

showEditFormでは、Userオブジェクトを作って、model.addAttribute("user", user)で画面に渡しています。

ここで指定した"user"という名前が、HTML側のth:object="${user}"と対応します。

model.addAttribute("user", user);




<form th:object="${user}">

この名前がずれると、Thymeleafでエラーになります。

たとえば、Controllerでmodel.addAttribute("userForm", user)としているのに、HTMLでth:object="${user}"と書くと、userが見つかりません。

入力フォーム画面

次に、入力フォーム画面を作ります。

ファイルの場所は、Spring Bootの一般的な構成なら次のようになります。

src/main/resources/templates/user/edit.html

edit.htmlのサンプルです。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>ユーザー編集</title>
</head>
<body>

<h1>ユーザー編集</h1>

<form th:action="@{/update}" th:object="${user}" method="post">

    <div>
        <label for="name">名前</label>
        <input type="text" th:field="*{name}" id="name" />
    </div>

    <div>
        <label for="email">メールアドレス</label>
        <input type="email" th:field="*{email}" id="email" />
    </div>

    <button type="submit">送信</button>

</form>

</body>
</html>




最初に示されたコードに、labelやHTML全体を追加した形です。

th:action="@{/update}"は、フォームの送信先URLを指定しています。

method="post"は、POST送信するという意味です。

th:object="${user}"は、このフォーム全体がuserオブジェクトと結びつくという意味です。

th:field="*{name}"は、userオブジェクトのnameプロパティと入力欄を結びつけます。

th:field="*{email}"は、userオブジェクトのemailプロパティと入力欄を結びつけます。

コード意味
th:action="@{/update}"送信先を/updateにする
th:object="${user}"フォームで使うオブジェクトをuserにする
th:field="*{name}"user.nameと入力欄を結びつける
th:field="*{email}"user.emailと入力欄を結びつける
method="post"POST送信する

th:fieldが実際に作るHTML

th:fieldは、単にvalueを入れるだけではありません。

id、name、valueを自動的に整えてくれます。

たとえば、次のThymeleafコードがあるとします。

<input type="text" th:field="*{name}" id="name" />




user.nameに「山崎太郎」が入っている場合、ブラウザに届くHTMLは次のようなイメージになります。

<input type="text" id="name" name="name" value="山崎太郎">




つまり、th:field="*{name}"によって、name="name"とvalue="山崎太郎"が作られます。

このname="name"が、フォーム送信時のパラメータ名になります。

emailも同じです。

<input type="email" th:field="*{email}" id="email" />




ブラウザに届くHTMLのイメージです。

<input type="email" id="email" name="email" value="yamazaki@example.com">




フォーム送信時には、次のような値が送られます。

name=山崎太郎
email=yamazaki@example.com





Spring Bootは、このnameとemailを見て、UserオブジェクトのsetName()とsetEmail()に値を入れます。

送信後の結果画面

送信後に確認画面を表示するため、result.htmlを作ります。

ファイルの場所です。

src/main/resources/templates/user/result.html

result.htmlのサンプルです。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>更新結果</title>
</head>
<body>

<h1>更新結果</h1>

<p th:text="${message}"></p>

<table border="1">
    <tr>
        <th>名前</th>
        <td th:text="${user.name}"></td>
    </tr>
    <tr>
        <th>メールアドレス</th>
        <td th:text="${user.email}"></td>
    </tr>
</table>

<p>
    <a th:href="@{/edit}">編集画面に戻る</a>
</p>

</body>
</html>




この画面では、POST送信されたUserオブジェクトを再びModelに入れ、Thymeleafで表示しています。

model.addAttribute("user", user);

そのため、result.htmlでは次のように参照できます。

<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>

th:textは、画面に文字を表示するための属性です。

th:fieldは入力欄とJavaオブジェクトを結びつけるための属性です。

属性役割よく使う場所
th:field入力フォームとプロパティを結びつけるinput、select、textarea
th:text値を画面に表示するp、td、span、div

動作の流れを確認しよう

今回のサンプルは、次の流れで動きます。

操作URL動き
編集画面を開く/editControllerがUserを作って画面へ渡す
フォームを表示するuser/edit.htmlth:fieldでnameとemailを入力欄へ表示する
送信ボタンを押す/updatePOSTでnameとemailを送る
Controllerで受け取るupdate(User user)Spring BootがUserに値を詰める
結果画面を表示するuser/result.html送信された値を表示する

特に重要なのは、updateメソッドの引数です。

@PostMapping("/update")
public String update(User user, Model model) {

ここでは、@RequestParamを1つずつ書いていません。

Spring Bootが、リクエストパラメータのnameとemailを見て、Userオブジェクトに自動で詰めてくれます。

この仕組みを、フォームバインディングと呼びます。

バインディングとは、「結びつける」という意味です。

画面の入力欄とJavaオブジェクトのプロパティを結びつけるイメージです。

@ModelAttributeを明示して書く場合

updateメソッドは、次のように@ModelAttributeを付けて書くこともできます。

@PostMapping("/update")
public String update(@ModelAttribute User user, Model model) {

    System.out.println("送信された名前: " + user.getName());
    System.out.println("送信されたメールアドレス: " + user.getEmail());

    model.addAttribute("user", user);
    model.addAttribute("message", "更新内容を受け取りました。");

    return "user/result";
}

@ModelAttributeは、リクエストパラメータをJavaオブジェクトに詰めるときによく使います。

新人エンジニアのうちは、フォームクラスやUserクラスで値を受け取る場合、@ModelAttributeを付けておくと意図がわかりやすくなります。

書き方意味
User userSpring BootがUserに値を入れる
@ModelAttribute User userリクエスト値をUserに入れることを明示する

よくあるミス1:model.addAttributeの名前が違う

次のようなミスはとても多いです。

Controller側です。

model.addAttribute("userForm", user);

HTML側です。

<form th:object="${user}">

ControllerではuserFormという名前で渡しているのに、HTMLではuserという名前で受け取ろうとしています。

この場合、Thymeleafはuserを見つけられません。

正しくは、名前を合わせます。

model.addAttribute("user", user);
<form th:object="${user}">

または、次のようにします。

model.addAttribute("userForm", user);
<form th:object="${userForm}">

名前合わせは、Thymeleafフォームの基本です。

宅配便の宛名と受け取り人名が違うと届かないのと同じです。

よくあるミス2:Userクラスにgetterやsetterがない

次のようなUserクラスでは、うまく動かないことがあります。

public class User {

    private String name;
    private String email;
}

フィールドだけありますが、getterとsetterがありません。

Spring Bootがフォーム値をUserに詰めるには、setName()やsetEmail()が必要です。

Thymeleafが値を表示するには、getName()やgetEmail()が必要です。

正しくは、次のように書きます。

public class User {

    private String name;
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

フォームで使うクラスには、基本的にgetterとsetterを用意しましょう。

よくあるミス3:th:objectなしで*{}を使う

次のようなHTMLはエラーになりやすいです。

<form th:action="@{/update}" method="post">
    <input type="text" th:field="*{name}" />
</form>

th:field="*{name}"の*{}は、th:objectで指定されたオブジェクトを前提にしています。

そのため、th:object="${user}"がないと、「どのオブジェクトのnameなのか」がわかりません。

正しくは次のようにします。

<form th:action="@{/update}" th:object="${user}" method="post">
    <input type="text" th:field="*{name}" />
</form>

th:objectとth:fieldはセットで考えましょう。

よくあるミス4:プロパティ名が違う

UserクラスのプロパティがuserNameなのに、HTMLでnameと書いているケースです。

public class User {

    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

この場合、HTMLでは次のように書く必要があります。

<input type="text" th:field="*{userName}" />

次の書き方では、nameプロパティが存在しません。

<input type="text" th:field="*{name}" />

Thymeleafのプロパティ名は、Javaのgetterと対応します。

JavaのgetterThymeleafの書き方
getName()*{name}
getEmail()*{email}
getUserName()*{userName}

このフォームを一言で説明すると

最初のコードを、もう一度見てみましょう。

<form th:action="@{/update}" th:object="${user}" method="post">
    <input type="text" th:field="*{name}" />
    <input type="email" th:field="*{email}" />
    <button type="submit">送信</button>
</form>

このフォームは、次の意味になります。

コード新人向けの説明
th:action="@{/update}"送信ボタンを押したら/updateへ送る
th:object="${user}"このフォームはuserオブジェクトを使う
method="post"POST方式で送信する
th:field="*{name}"userのnameと入力欄を結びつける
th:field="*{email}"userのemailと入力欄を結びつける
button type="submit"フォームを送信するボタン

つまり、このコードは「userというJavaオブジェクトのnameとemailを画面で編集し、/updateへ送信するフォーム」です。

まとめ

Thymeleafのth:objectとth:fieldを使うと、HTMLフォームとJavaオブジェクトを簡単に結びつけられます。

今回のサンプルでは、Userクラスのnameとemailを入力欄に表示し、送信後にControllerでUserとして受け取りました。

重要ポイント内容
th:objectフォーム全体で使うオブジェクトを指定する
th:fieldオブジェクトのプロパティと入力欄を結びつける
model.addAttributeControllerからThymeleafへデータを渡す
@PostMappingフォーム送信後の処理を受け取る
@ModelAttributeフォーム値をJavaオブジェクトに詰めることを明示する

新人エンジニアは、まず次の対応関係を覚えてください。

model.addAttribute("user", user)
        ↓
th:object="${user}"
        ↓
th:field="*{name}"
        ↓
UserクラスのgetName()、setName()

Thymeleafフォームでエラーが出たときは、「Model名」「th:objectの名前」「th:fieldのプロパティ名」「Javaクラスのgetterとsetter」を順番に確認しましょう。

今後の学習では、@ModelAttribute、バリデーション、BindingResult、入力エラー時の再表示、DB更新処理の流れを順番に学ぶとよいです。まずは、画面の入力欄とJavaオブジェクトがどう結びついているのかを、手を動かして確認してください!

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。