Thymeleafのth:objectの使い方を完全ガイド!初心者でもわかるフォームデータの連携
生徒
「Thymeleafでフォームとサーバー側のデータを連携させる方法ってありますか?」
先生
「はい、Thymeleafではth:objectという属性を使って、フォーム全体をサーバーサイドのデータオブジェクトと簡単に連携させることができます。」
生徒
「具体的にはどのように使うんですか?」
先生
「それでは、th:objectの使い方を順を追って見ていきましょう!」
1. th:objectとは?
th:objectは、Thymeleafのテンプレートエンジンでフォーム全体をサーバーサイドのモデルオブジェクトと連携するための属性です。
主にSpring MVCのコントローラーと連携して、ユーザーが入力したデータを受け取ったり、初期値を設定するために使用します。
例えば、フォームを使ってユーザーの名前やメールアドレスを入力させ、それをバックエンドのJavaオブジェクトにマッピングする際に活用します。
th:objectを正しく設定することで、フォーム内の各入力要素が自動的にオブジェクトのプロパティと紐づけられます。
2. th:objectの基本的な使い方
th:objectを使用するには、フォームタグにth:object属性を追加します。
ここでは、ユーザーの名前とメールアドレスを入力する簡単な例を見てみましょう。
<form th:action="@{/submit}" th:object="${user}" method="post">
<label for="name">名前:</label>
<input type="text" id="name" th:field="*{name}" />
<label for="email">メールアドレス:</label>
<input type="email" id="email" th:field="*{email}" />
<button type="submit">送信</button>
</form>
上記の例では、th:objectで指定した${user}オブジェクトに、入力された名前とメールアドレスがバインドされます。
th:fieldを使うことで、各入力フィールドとuserオブジェクトのプロパティが紐づけられます。
3. th:objectを使うメリット
th:objectを使う最大のメリットは、テンプレートとサーバーサイドのデータオブジェクトが簡単に連携できることです。
以下にその主なメリットをまとめます。
- テンプレートの保守性が向上し、コードが簡潔になる。
- Spring MVCと連携することで、データバインディングやフォームバリデーションが容易に行える。
- 初期値の設定やエラーメッセージの表示が簡単になる。
例えば、ユーザー登録フォームで初期値を設定したい場合、コントローラー側でuserオブジェクトを作成し、初期値をセットしてビューに渡すことで、フォームに反映させることができます。
4. th:objectを使ったフォームバリデーション
Spring MVCとThymeleafを組み合わせることで、フォームバリデーションを簡単に実現できます。 例えば、入力必須の項目にエラーメッセージを表示する例を以下に示します。
<form th:action="@{/submit}" th:object="${user}" method="post">
<label for="name">名前:</label>
<input type="text" id="name" th:field="*{name}" />
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
<label for="email">メールアドレス:</label>
<input type="email" id="email" th:field="*{email}" />
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
<button type="submit">送信</button>
</form>
この例では、th:errorsを使って、エラーが発生したフィールドに対応するエラーメッセージを表示しています。
これにより、ユーザーがどの項目でエラーが発生したかを簡単に確認できます。
5. th:objectを使用する際の注意点
th:objectを使う際には、以下の点に注意してください。
- サーバーサイドで正しいオブジェクトがコントローラーから渡されていることを確認する。
- オブジェクトのプロパティ名と
th:fieldの指定が一致していること。 - フォーム送信時に適切なエラーハンドリングを実装すること。
これらを考慮することで、th:objectを使ったフォームの動作を正確に管理できます。
6. ネストしたオブジェクトとコレクションのバインド(住所・電話番号など)
th:objectは、ネストしたプロパティ(例:address.city)やリスト型(例:phones)にも対応できます。
住所や電話番号のように階層構造や複数行の入力を扱うときは、プロパティパスとインデックスを正しく指定します。
<form th:action="@{/users/save}" th:object="${user}" method="post">
<!-- ネストしたオブジェクト -->
<div class="mb-3">
<label>郵便番号</label>
<input type="text" th:field="*{address.postalCode}" />
</div>
<div class="mb-3">
<label>都道府県</label>
<input type="text" th:field="*{address.prefecture}" />
</div>
<div class="mb-3">
<label>市区町村</label>
<input type="text" th:field="*{address.city}" />
</div>
<!-- コレクション(phones) -->
<div th:each="ph, stat : *{phones}" class="mb-3">
<label>電話番号 [[${stat.index}+1]] 件目</label>
<!-- インデックスを使ってコレクション要素にバインド -->
<input type="text" th:field="*{phones[__${stat.index}__].number}" placeholder="090-xxxx-xxxx" />
<select th:field="*{phones[__${stat.index}__].type}">
<option value="HOME">自宅</option>
<option value="MOBILE">携帯</option>
<option value="WORK">勤務先</option>
</select>
</div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
- ネストはドット記法(
*{address.city})で指定。 - リストはインデックス(
*{phones[__${stat.index}__].number})で要素を特定。 - 動的に行を追加する場合も、同じインデックス規則を守ると確実にバインドされます。
7. @ModelAttributeとth:objectの連携(コントローラー側の基本)
フォーム表示(GET)と送信(POST)で同じ属性名を使うのがポイントです。@ModelAttribute("user")の名前と、
テンプレート側のth:object="${user}"を揃えると、双方向のデータ連携が自然に機能します。
@Controller
@RequestMapping("/users")
public class UserController {
@GetMapping("/new")
public String showForm(@ModelAttribute("user") UserForm form) {
// 初期値(例:電話番号1件だけ用意)
form.getPhones().add(new PhoneForm());
return "users/form"; // Thymeleafテンプレート
}
@PostMapping("/save")
public String save(
@Valid @ModelAttribute("user") UserForm form,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "users/form"; // エラー時は同じテンプレートへ戻す
}
// TODO: 保存処理
return "redirect:/users/complete";
}
}
@ModelAttribute("user")とth:object="${user}"の名称一致が重要。- バリデーションは
@ValidとBindingResultで受け取り、テンプレートの#fieldsと連携。 - 初期表示時にコレクション要素を用意しておくと、空のフォームが描画しやすくなります。
8. 既存データの編集フォーム(初期値表示・IDの保持)
編集フォームでは、th:fieldが自動で初期値を反映します。識別子(ID)はhiddenで保持し、
CSRF対策やメソッド、アクションはアプリの規約に合わせて設定します。
<form th:action="@{/users/update}" th:object="${user}" method="post">
<input type="hidden" th:field="*{id}" />
<div class="mb-3">
<label>氏名</label>
<input type="text" th:field="*{name}" />
</div>
<div class="mb-3">
<label>メール</label>
<input type="email" th:field="*{email}" />
</div>
<!-- 既存の住所・電話もそのまま表示される -->
<div class="mb-3">
<label>市区町村</label>
<input type="text" th:field="*{address.city}" />
</div>
<button type="submit" class="btn btn-success">更新</button>
</form>
th:fieldは既存値を自動反映(th:valueの併用は不要)。- IDは
<input type="hidden" th:field="*{id}">で送信時に失われないよう保持。 - 表示専用の値は
th:text、編集可能な値はth:fieldを使い分け。
9. セレクト・ラジオ・チェックボックス・ファイルとの連携
選択系やファイル入力もth:objectと相性抜群です。選択肢のリストをモデルに用意しておけば、値と表示名を分けて安全にバインドできます。
<form th:action="@{/profile/save}" th:object="${user}" method="post" enctype="multipart/form-data">
<!-- セレクト(単一) -->
<div class="mb-3">
<label>都道府県</label>
<select th:field="*{prefecture}">
<option value="" disabled>選択してください</option>
<option th:each="p : ${prefectureList}"
th:value="${p.code}"
th:text="${p.label}"></option>
</select>
</div>
<!-- ラジオ(性別など) -->
<div class="mb-3" th:each="g : ${genders}">
<label>
<input type="radio" th:field="*{gender}" th:value="${g.code}" />
<span th:text="${g.label}"></span>
</label>
</div>
<!-- チェックボックス(複数ロール) -->
<div class="mb-3" th:each="r : ${roleOptions}">
<label>
<input type="checkbox" th:field="*{roles}" th:value="${r}" />
<span th:text="${r}"></span>
</label>
</div>
<!-- 複数選択セレクト -->
<div class="mb-3">
<label>興味のある分野</label>
<select multiple th:field="*{interests}">
<option th:each="it : ${interestOptions}"
th:value="${it}"
th:text="${it}"></option>
</select>
</div>
<!-- ファイルアップロード(MultipartFile avatar) -->
<div class="mb-3">
<label>プロフィール画像</label>
<input type="file" th:field="*{avatar}" />
</div>
<button type="submit" class="btn btn-primary">登録</button>
</form>
- 選択肢は「値(
code)と表示名(label)」を分離してモデルに用意。 - 複数選択はコレクション型プロパティ(
List<String>など)にバインド。 - ファイルは
enctype="multipart/form-data"を忘れず、サーバー側でMultipartFileを受け取ります。
まとめ
本記事では、Thymeleafのth:object属性について詳しく解説しました。この属性は、フォーム全体をサーバーサイドのモデルオブジェクトと連携させるための重要な機能です。
th:objectを使うことで、フォーム入力データとバックエンドのプロパティを効率的に紐づけることができます。また、th:fieldと組み合わせることで、個々の入力要素のバインディングが簡単に実現します。
特に、フォームバリデーションの実装やエラーメッセージの表示がスムーズになるため、ユーザー体験を向上させるフォーム作成に役立ちます。 以下に、もう一度基本的なフォーム例を掲載しますので、ぜひ実際に試してみてください。
<form th:action="@{/submit}" th:object="${user}" method="post">
<label for="username">ユーザー名:</label>
<input type="text" id="username" th:field="*{username}" />
<label for="email">メールアドレス:</label>
<input type="email" id="email" th:field="*{email}" />
<button type="submit">登録</button>
</form>
このフォームでは、usernameとemailのデータがサーバーサイドのuserオブジェクトにバインドされます。適切にコントローラーを設定することで、入力値を受け取ることができます。
生徒
「th:objectを使うと、フォームとバックエンドの連携がすごく簡単になりますね!」
先生
「その通りです。th:objectを活用すれば、データバインディングが効率的になりますし、テンプレートの保守性も向上しますよ。」
生徒
「フォームバリデーションも簡単に実装できるのが良いですね。これからの開発でどんどん使ってみたいです!」
先生
「ぜひ取り入れてみてください。次は、さらに高度なフォーム機能も試してみるといいですね。」