Thymeleaf th:objectの使い方を完全ガイド!フォームオブジェクトの基本を初心者向けに解説
新人
「Springで入力フォームを作ってたら、th:objectっていうのが出てきたんですけど、これって何ですか?」
先輩
「それはThymeleafでフォームオブジェクトとデータバインドするための機能なんだよ。便利だから覚えておくといいよ。」
新人
「データバインドって、コントローラとHTMLをつなぐってことですよね?フォームオブジェクトってのもよく分かってなくて…」
先輩
「なるほど、それじゃあまずはフォームオブジェクトが何かから説明しようか!」
1. フォームオブジェクトとは何か?
フォームオブジェクトとは、ユーザーがHTMLフォームで入力した値を受け取るためのJavaクラスです。複数の入力項目をひとつのオブジェクトにまとめて扱えるようになります。
たとえばログインフォームでは、「ユーザー名」と「パスワード」の2つの値をひとまとめにして受け取りたいですよね。そのときに使うのがフォームオブジェクトです。
以下はLoginFormという名前のフォームオブジェクトの例です。
public class LoginForm {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
このようなクラスをSpringの@ModelAttributeで受け取れば、画面から送信されたデータを自動的にJavaオブジェクトとして扱えます。これが「データバインド」の仕組みです。
2. Thymeleafのth:objectとは何か?
th:objectは、Thymeleafでフォームとフォームオブジェクトを関連付けるための属性です。フォーム全体に「このオブジェクトを使うよ」と宣言する役割を持ちます。
たとえば以下のように記述することで、フォーム全体がloginFormというオブジェクトと結びつきます。
<form th:action="@{/login}" th:object="${loginForm}" method="post">
<label for="username">ユーザー名:</label>
<input type="text" th:field="*{username}" id="username" />
<label for="password">パスワード:</label>
<input type="password" th:field="*{password}" id="password" />
<button type="submit">ログイン</button>
</form>
th:objectで指定したオブジェクトをもとに、th:fieldが個々の入力要素をバインドします。*{username}や*{password}という書き方を使うことで、フォームオブジェクトのプロパティと自動的に対応づけられるのです。
この仕組みを使うことで、HTMLとJavaのコードをきれいに分けながら、安全かつ効率的にフォーム処理ができるようになります。
3. @ModelAttributeを使ってフォームオブジェクトをControllerに渡す方法
フォームオブジェクトは、Springの@ModelAttributeを使ってコントローラと連携します。これにより、Thymeleafで入力されたデータを自動的にJavaオブジェクトに変換できます。
たとえば、ログイン画面を表示するGETリクエストでは、空のフォームオブジェクトをModelに追加して、HTML側で使用できるようにします。
@Controller
public class LoginController {
@GetMapping("/login")
public String showForm(Model model) {
model.addAttribute("loginForm", new LoginForm());
return "login";
}
}
上記のように記述することで、Thymeleafテンプレート内で${loginForm}としてフォームオブジェクトが使えるようになります。これはth:objectと連携するために必須の準備です。
次に、実際にユーザーがフォームに入力した値をPOSTで受け取る方法を見てみましょう。
@PostMapping("/login")
public String processLogin(@ModelAttribute LoginForm loginForm) {
// loginFormにフォームの値が自動でバインドされている
System.out.println("ユーザー名: " + loginForm.getUsername());
System.out.println("パスワード: " + loginForm.getPassword());
return "redirect:/home";
}
@ModelAttributeを使うことで、フォームの各項目とJavaオブジェクトのフィールドが自動でマッピングされるのです。これはSpring MVCの非常に強力な機能であり、初心者が覚えておくべきポイントです。
4. th:objectとth:fieldの連携でデータをフォームに埋め込む方法
Thymeleafでは、th:objectとth:fieldを組み合わせることで、フォームの入力欄に初期値を表示したり、再表示時に入力済みのデータを埋め込むことができます。
たとえば、ユーザー名にあらかじめ値を入れたい場合、コントローラでフォームオブジェクトに値を設定して返します。
@GetMapping("/login")
public String showForm(Model model) {
LoginForm form = new LoginForm();
form.setUsername("taro@example.com");
model.addAttribute("loginForm", form);
return "login";
}
このようにオブジェクトに値を設定しておけば、HTMLでは以下のように書くだけで、入力欄に値が表示されます。
<form th:action="@{/login}" th:object="${loginForm}" method="post">
<label for="username">ユーザー名:</label>
<input type="text" th:field="*{username}" id="username" />
</form>
th:fieldを使うことで、フォームオブジェクトのフィールドと入力欄がバインドされ、値が自動的に挿入されます。th:valueではなくth:fieldを使うのがポイントです。
また、バリデーションエラー時の再描画でも、th:fieldを使えば入力内容が保持されるため、非常に便利です。
5. 入力フォームとオブジェクトのマッピングの仕組み
フォームオブジェクトとHTMLのフォームを連携させることで、Spring MVCが自動で「マッピング=値の受け渡し」を行います。この仕組みの中核が「データバインド」です。
ユーザーがフォームを送信すると、ブラウザからは名前付きのパラメータが送られます。たとえば以下のような入力をすると:
<input type="text" th:field="*{username}" />
送信時にusername=入力値という形式でリクエストされます。Springはその名前と同じフィールドを持つJavaクラス(今回ならLoginForm)を探して、自動で対応させてくれるのです。
このとき、フォームに使用するname属性はth:fieldが自動的に生成してくれます。手動でnameを書かなくてよいのはこのためです。
具体的には以下のように展開されます。
<input type="text" id="username" name="username" value="taro@example.com" />
このようにして、Thymeleafのth:objectとth:fieldを使えば、HTMLとJavaのオブジェクトを効率的かつ安全にバインドできます。特に、複数のフォーム項目がある場合でも、オブジェクトのフィールドと一括でマッピングされるため、コードがすっきりと保てます。
また、Springが用意しているBindingResultや@Validと組み合わせることで、バリデーション処理もシンプルに行えるようになります。
6. th:objectを使う際によくあるミス
Thymeleafのth:objectを使うと、フォーム オブジェクトとのバインドが簡単になりますが、初心者の方がつまずきやすいポイントもいくつかあります。以下によくあるバインド エラーとその原因を紹介します。
① コントローラでオブジェクトをModelに追加し忘れる
フォームを表示する@GetMappingで、フォーム オブジェクトをModelに追加していないと、th:object="${loginForm}"でバインドできず、実行時にNullPointerExceptionやエラーになります。
@GetMapping("/login")
public String showForm(Model model) {
// 以下が抜けているとエラーになります
model.addAttribute("loginForm", new LoginForm());
return "login";
}
② th:fieldの指定ミス
Thymeleafのth:fieldは、必ずth:objectの内部で使用する必要があります。これを忘れてth:fieldだけ書いてしまうと、どのオブジェクトのフィールドを指しているか分からなくなり、フォームに値が表示されなかったり、送信できなくなります。
<!-- th:objectがないとエラーやバインド失敗につながります -->
<form method="post">
<input type="text" th:field="*{username}" />
</form>
③ フィールド名のタイプミス
オブジェクトのプロパティ名と一致していない名前をth:fieldで指定すると、バインドできません。たとえば、Javaのクラスではusernameとなっているのに、HTMLでは*{userName}としてしまうとエラーになります。
初心者のうちは、オブジェクトのフィールド名と正確に一致するように注意しましょう。
7. th:objectと個別th:属性の違いと使い分け
Thymeleafでは、フォームに使える属性としてth:objectのほかに、th:valueやth:nameなど、個別に指定する方法もあります。それぞれの使い分けを理解することが重要です。
【th:object+th:field】
複数の入力項目があり、フォーム オブジェクトにまとめてバインドしたい場合はth:objectとth:fieldの組み合わせが最適です。
<form th:object="${loginForm}" method="post">
<input type="text" th:field="*{username}" />
<input type="password" th:field="*{password}" />
</form>
【th:valueやth:nameのみを使う方法】
フォーム オブジェクトを使わず、単純な1項目だけをバインドしたい場合には、個別にth:valueやth:nameを使うこともあります。
<form method="post">
<input type="text" th:name="'username'" th:value="'初期値'" />
</form>
ただしこの方法は、値の埋め込みや再描画時の保持などを自分で管理する必要があるため、初心者の方にはth:objectを使う方が確実で安全です。
8. 実務で役立つ使い方やバリデーションとの連携Tips
実務では、フォームの値をただ受け取るだけでなく、入力ミスや未入力に対してエラーメッセージを表示することも求められます。そのためにSpringでは@ValidとBindingResultを使ったバリデーションが非常に便利です。
① フォームオブジェクトにバリデーションアノテーションをつける
public class LoginForm {
@NotEmpty(message = "ユーザー名は必須です")
private String username;
@NotEmpty(message = "パスワードは必須です")
private String password;
// getter/setterは省略
}
② コントローラで@ValidとBindingResultを使う
@PostMapping("/login")
public String processLogin(
@Valid @ModelAttribute LoginForm loginForm,
BindingResult result) {
if (result.hasErrors()) {
return "login"; // エラーがあればフォームに戻す
}
// 正常処理
return "redirect:/home";
}
③ エラー表示をHTMLに追加する
<form th:object="${loginForm}" method="post" th:action="@{/login}">
<label>ユーザー名:</label>
<input type="text" th:field="*{username}" />
<div th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></div>
<label>パスワード:</label>
<input type="password" th:field="*{password}" />
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
<button type="submit">ログイン</button>
</form>
このように、th:objectを使えば、バリデーションの結果を自動で表示できる仕組みも備わっており、実務でも安心して使えます。
エラーがあるフィールドには赤枠をつけたり、説明文を出すなど、ユーザー体験を向上させる工夫も可能です。まずは基本をしっかり押さえて、焦らず慣れていけば大丈夫ですよ。
まとめ
ここまで、Thymeleafでフォームを構築する際に欠かせないth:objectとフォームオブジェクトの役割について、基本から丁寧に振り返ってきました。フォームオブジェクトは、複数の入力値をひとまとめにして扱える便利な仕組みであり、実際のWebアプリケーション開発では、ログインフォーム、会員登録フォーム、プロフィール編集画面など、あらゆる場面で活用されます。フォームオブジェクトとth:objectを組み合わせることで、ユーザーから送られてくる値を安全かつ正確にJava側へ受け渡すことができ、複雑な処理も見通しよく記述できます。
特にThymeleafのth:objectは、フォーム全体に「このオブジェクトをこのフォームで使う」という宣言を行う役割を持ち、th:fieldとセットで使うことで、より明確で直感的なHTML構造が保たれます。これは、テンプレートエンジンとしてのThymeleafの強みでもあり、フロントエンド開発者とバックエンド開発者の両方にとって理解しやすい構造を作るための鍵となります。
また、フォームオブジェクトはSpringのデータバインド機能と密接に関わっており、@ModelAttributeと組み合わせることで、画面から送信される値を自動でJavaオブジェクトに変換できます。これにより、余分な処理を避けながら、実装の保守性を高めることができます。たとえばログイン、ユーザー登録、検索フォームなど、日常的に使われる画面において、フォームオブジェクトがどれほど重要な役割を持つかが理解できるはずです。
以下では、記事の内容に沿った形で基本構造を見直せるようにサンプルコードをまとめています。
フォームオブジェクトとth:objectを使ったサンプル
<form th:action="@{/submit}" th:object="${formData}" method="post" class="card p-3">
<label for="username" class="fw-bold">ユーザー名</label>
<input id="username" type="text" th:field="*{username}" class="form-control mb-2"/>
<label for="email" class="fw-bold">メールアドレス</label>
<input id="email" type="email" th:field="*{email}" class="form-control mb-2"/>
<label for="comment" class="fw-bold">コメント</label>
<textarea id="comment" th:field="*{comment}" class="form-control mb-3"></textarea>
<button class="btn btn-primary">送信</button>
</form>
このように、フォームオブジェクトを活用することで、複数の入力項目を整理しながら、動的な入力フォームを簡潔に構築できます。また、th:objectはフォーム全体の前提となるオブジェクトを1行で明示できるため、複雑な画面でも混乱を避けられます。特に、th:fieldと組み合わせると、各入力項目がオブジェクトのどのプロパティと対応しているのかがひと目でわかり、長期的な開発や保守の中でも非常に役立ちます。
実際の現場では、フォームの項目が増えたり、バリデーションが追加されたり、表示内容が変更されることがよくありますが、フォームオブジェクトとThymeleafの仕組みを理解しておくことで、こうした変更に柔軟に対応可能となります。また、HTML側とJava側が明確に結びつく構造は、エラー処理や入力チェックとも相性がよく、ユーザーにとって使いやすいUIを提供しやすくなります。今回の内容をしっかり身につけることで、より信頼性の高いフォーム処理ができるようになるでしょう。
生徒:「今日はフォームオブジェクトとth:objectの関係がよく分かりました。HTML側で書くコードもすっきりして見やすかったです。」
先生:「そうですね。フォームの処理は開発では必ず出てくる部分なので、今回の内容を理解しておくと後々すごく役立ちますよ。」
生徒:「th:fieldとの組み合わせで、自動的にプロパティと結びつくのが便利だと感じました。入力項目が増えても整理しやすそうです。」
先生:「そのとおりです。特に大規模な画面だと、どこがどの値につながっているのか分かりにくくなるので、Thymeleafの構文はとても効果的です。」
生徒:「Spring側で@ModelAttributeを使って受け取る流れも理解できました。フォームの仕組みが意外と奥深いんですね。」
先生:「フォーム処理は想像以上に奥が深いですが、その分理解すると開発の幅が一気に広がります。これからも一緒にしっかり学んでいきましょう。」
生徒:「はい!もっと実践的なフォーム作成にも挑戦してみます!」