Thymeleaf th:fieldの基本と便利な使い方を初心者向けに解説!
新人
「SpringでHTMLフォームを作りたいんですが、th:fieldって何のために使うんですか?」
先輩
「それはThymeleafでフォーム入力値をJavaのオブジェクトと自動でバインドするための属性だよ。とても便利なんだ。」
新人
「Javaオブジェクトに自動で? どんな風に書けばいいんですか?」
先輩
「じゃあ、HTMLフォームとth:fieldの基本から順番に説明するよ。」
1. HTMLフォームの基本とinput要素
まずはSpringとは関係なく、通常のHTMLでフォームを作る場合の基本構造を見てみましょう。以下はユーザー名とパスワードを入力するフォームの例です。
<form action="/login" method="post">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">ログイン</button>
</form>
このようにinputタグのname属性を使って、サーバー側にデータを送信します。ただし、Springでフォームバインドを使いたい場合は、これだけでは不十分です。コントローラ側で受け取るモデルオブジェクトと連携するためには、Thymeleafのth:objectやth:fieldを使う必要があります。
2. Thymeleafのth:fieldとは?(フォームとの連携)
Thymeleafにおけるth:field属性は、Spring MVCのフォームバインド機能と連携して、HTMLフォームの各フィールドをJavaのオブジェクトと結びつけるために使います。
例えば、以下のようなUserFormクラスがあるとします。
public class UserForm {
private String username;
private String password;
// getterとsetterは省略
}
このUserFormを使って、コントローラでは以下のようにモデルに登録します。
@Controller
public class LoginController {
@GetMapping("/login")
public String showLoginForm(Model model) {
model.addAttribute("userForm", new UserForm());
return "login";
}
@PostMapping("/login")
public String submitLogin(@ModelAttribute UserForm userForm) {
// ログイン処理
return "result";
}
}
この場合、Thymeleafのテンプレートでは次のようにth:objectとth:fieldを使います。
<form th:action="@{/login}" th:object="${userForm}" method="post">
<input type="text" th:field="*{username}" />
<input type="password" th:field="*{password}" />
<button type="submit">ログイン</button>
</form>
th:objectでバインド対象のオブジェクトを指定し、th:fieldでオブジェクトのフィールド名を指定します。*{username}と書くことで、userForm.getUsername()のように連携してくれるのです。
初心者が混乱しやすいポイントとして、th:fieldを使うと、自動的にname属性やid属性も自動で生成されます。そのため、手動でnameを指定する必要はありません。
補足:HTMLに出力される実際のコード
上記のth:fieldを使ったフォームは、ブラウザに表示されると以下のようなHTMLに変換されます。
<form action="/login" method="post">
<input type="text" id="username" name="username" value="">
<input type="password" id="password" name="password" value="">
<button type="submit">ログイン</button>
</form>
このように、th:fieldを使うことで、idやname、valueなどの属性を自動でバインドしてくれるため、記述ミスを防げて非常に便利です。
ラジオボタンやチェックボックスの場合
th:fieldは、テキスト入力だけでなく、ラジオボタンやチェックボックスにも使えます。
<label><input type="radio" th:field="*{gender}" value="male"> 男性</label>
<label><input type="radio" th:field="*{gender}" value="female"> 女性</label>
<label><input type="checkbox" th:field="*{agree}"> 利用規約に同意する</label>
ラジオボタンでは同じフィールド名に対して異なるvalueを持たせることで、1つだけ選択されるようになります。チェックボックスではboolean型のフィールドと連携することで、オン・オフの状態を管理できます。
select要素(プルダウン)との連携
selectタグにもth:fieldを使うことができます。以下は都道府県の一覧を選択肢として表示する例です。
<select th:field="*{prefecture}">
<option value="tokyo">東京都</option>
<option value="osaka">大阪府</option>
<option value="hokkaido">北海道</option>
</select>
コントローラ側でuserForm.setPrefecture("osaka")のようにしておけば、「大阪府」が初期選択されます。
3. th:fieldを使ったバインドの具体例(オブジェクトとの連携)
ここでは、Thymeleafのth:fieldを使って、複数のフィールドを含むフォーム入力をオブジェクトにバインドする具体的な例を紹介します。初心者向けに、エンティティクラスとテンプレート、そしてコントローラの流れを丁寧に見ていきましょう。
まずはユーザー登録用のフォームオブジェクトを定義します。
public class RegisterForm {
private String name;
private String email;
private int age;
private String password;
// getterとsetterは省略
}
次にコントローラ側でこのフォームをモデルに追加します。
@Controller
public class RegisterController {
@GetMapping("/register")
public String showForm(Model model) {
model.addAttribute("registerForm", new RegisterForm());
return "register";
}
@PostMapping("/register")
public String submitForm(@ModelAttribute RegisterForm registerForm) {
// 登録処理を行う
return "result";
}
}
そして、Thymeleafテンプレートでは以下のようにth:objectとth:fieldを活用して、オブジェクトとフォーム入力を連携させます。
<form th:action="@{/register}" th:object="${registerForm}" method="post">
<label>名前: <input type="text" th:field="*{name}"></label><br>
<label>メール: <input type="email" th:field="*{email}"></label><br>
<label>年齢: <input type="number" th:field="*{age}"></label><br>
<label>パスワード: <input type="password" th:field="*{password}"></label><br>
<button type="submit">登録</button>
</form>
このようにth:fieldを使用することで、オブジェクトのフィールドとフォームの値が自動的に結びつきます。初心者が混乱しやすい点としては、th:objectとセットで使わないとth:fieldが正しく動作しないことがあります。この点に注意してください。
4. 入力値の保持とエラーメッセージの表示
フォームで入力された値にエラーがあった場合、そのまま再表示して入力値を保持したり、エラーメッセージを表示するのもThymeleafでは簡単に行えます。ここでは@ValidとBindingResultを使った例を紹介します。
まず、フォームクラスにバリデーションアノテーションを追加します。
public class RegisterForm {
@NotBlank(message = "名前を入力してください")
private String name;
@Email(message = "正しいメールアドレスを入力してください")
private String email;
@Min(value = 18, message = "18歳以上で登録できます")
private int age;
@Size(min = 6, message = "パスワードは6文字以上で入力してください")
private String password;
// getterとsetterは省略
}
コントローラでは次のように@ValidとBindingResultをセットで使います。
@PostMapping("/register")
public String submitForm(@Valid @ModelAttribute RegisterForm registerForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "register";
}
// 成功時の処理
return "result";
}
Thymeleafテンプレートでエラーを表示するには、th:errorsを使います。以下のようにフィールドごとにエラーを表示できます。
<form th:action="@{/register}" th:object="${registerForm}" method="post">
<label>名前: <input type="text" th:field="*{name}"></label>
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></div>
<label>メール: <input type="email" th:field="*{email}"></label>
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
<label>年齢: <input type="number" th:field="*{age}"></label>
<div th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></div>
<label>パスワード: <input type="password" th:field="*{password}"></label>
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
<button type="submit">登録</button>
</form>
このようにすると、フォーム入力時にバリデーションエラーがあった場合でも、入力値は保持され、該当フィールドの直下にエラーメッセージが表示されます。初心者が間違いやすい点としては、BindingResultの位置が@ModelAttributeの直後でないと無効になることです。注意しましょう。
5. th:fieldで扱えるフォーム部品(input、textarea、selectなど)
Thymeleafのth:field属性は、さまざまな種類のフォーム部品で使うことができます。ここでは初心者向けに、よく使われる代表的なフォーム要素との組み合わせを紹介します。
テキストエリア(textarea)
複数行のテキスト入力にはtextareaを使いますが、Thymeleafでもth:fieldを使ってバインドできます。
<label>自己紹介:</label>
<textarea th:field="*{introduction}"></textarea>
これにより、入力された内容がJavaのオブジェクトと連携され、再表示時にも内容が保持されます。
セレクトボックス(select)
select要素とth:fieldを組み合わせることで、リストから選択された値を自動でバインドできます。
<select th:field="*{job}">
<option value="engineer">エンジニア</option>
<option value="designer">デザイナー</option>
<option value="manager">マネージャー</option>
</select>
選択肢の値は、Javaオブジェクトのフィールドに対応する値として渡されます。
チェックボックスとラジオボタン
チェックボックスとラジオボタンも、th:fieldで簡単に使えます。
<label><input type="checkbox" th:field="*{subscribe}"> メール配信を希望する</label>
<label><input type="radio" th:field="*{gender}" value="male"> 男性</label>
<label><input type="radio" th:field="*{gender}" value="female"> 女性</label>
チェックボックスの場合、バインド先はboolean型のフィールドとなり、チェックの有無で値が変わります。ラジオボタンは同じth:fieldで複数の選択肢を表示する形式になります。
このように、th:fieldはフォーム入力に必要な主要な部品すべてに対応しており、初心者でも簡単にオブジェクトとのデータ連携を実現できます。
6. よくあるバインドエラーとth:fieldの注意点(nullや型不一致など)
初心者がThymeleafのth:fieldを使ってフォームバインドを行うとき、最もつまずきやすいのが「バインド エラー」です。特に、nullの取り扱いや型の不一致によるエラーは頻出で、エラー画面に赤いスタックトレースが表示されて驚いてしまうこともあります。
たとえば、フォームクラスで次のような定義をしているとします。
public class UserForm {
private int age;
// getter/setter省略
}
このとき、ageが数値ではなく空欄のまま送信された場合、Springはint型にnullを入れることができず、バインド エラーになります。これを避けるには、ラッパークラスであるInteger型を使うようにしましょう。
private Integer age;
また、フォームの値を受け取るときに、Thymeleafテンプレート上で指定しているフィールド名が実際のJavaクラスに存在しない場合も、th:fieldは正しく機能せず、空のフォームや例外の原因になります。
このようなケースに備えて、th:fieldを記述する際は、バインド対象のフィールド名とJava側のプロパティ名が一致しているか、ラッパークラスを使ってnull対応しているかを常に確認する習慣をつけておきましょう。
7. 実務で役立つth:fieldの応用例(リストやネスト構造への対応)
実際の業務では、単純な文字列や数値の入力だけでなく、「リスト形式の入力」や「ネストしたオブジェクト」へのフォームバインドが求められるケースがよくあります。Thymeleafのth:fieldは、こうした複雑な構造にも対応できます。
リスト構造とのバインド
たとえば、複数の電話番号を入力するような場面を考えてみましょう。
public class ContactForm {
private List<String> phones;
// getter/setter省略
}
このとき、Thymeleafテンプレートではインデックス番号を使ってth:fieldを指定します。
<form th:action="@{/contact}" th:object="${contactForm}" method="post">
<input type="text" th:field="*{phones[0]}" placeholder="電話番号1"><br>
<input type="text" th:field="*{phones[1]}" placeholder="電話番号2"><br>
<button type="submit">送信</button>
</form>
このように書くことで、phonesリストの0番目、1番目の要素にそれぞれバインドされます。フォーム側で空のリストが送られてくることもあるので、コントローラでは初期化しておくと安全です。
model.addAttribute("contactForm", new ContactForm(List.of("", "")));
ネストしたオブジェクトへのバインド
次に、親オブジェクトの中に別のオブジェクトを持つ「ネスト構造」の例を見てみましょう。
public class EmployeeForm {
private String name;
private Department department;
// getter/setter省略
}
public class Department {
private String code;
private String name;
// getter/setter省略
}
このような場合、Thymeleafではドット記法でth:fieldを指定します。
<form th:action="@{/employee}" th:object="${employeeForm}" method="post">
<label>社員名: <input type="text" th:field="*{name}"></label><br>
<label>部署コード: <input type="text" th:field="*{department.code}"></label><br>
<label>部署名: <input type="text" th:field="*{department.name}"></label><br>
<button type="submit">登録</button>
</form>
このように、th:fieldはリストやネスト構造にも柔軟に対応できるため、実務でも非常に役立ちます。初心者が注意すべき点は、ネストしたオブジェクトのインスタンスがnullのままだとバインド時に例外が発生するため、コントローラで必ず初期化しておく必要があることです。
8. フォーム送信とコントローラの連携(POSTの受け取り、ModelAttributeなど)
最後に、フォーム送信後にth:fieldでバインドされた値を、Springの@Controllerクラスでどのように受け取るかについて説明します。フォームとコントローラの連携は、初心者がつまずきやすいポイントなので、順を追って丁寧に解説します。
まず、Thymeleaf側のフォームにはth:actionとmethod="post"を設定し、th:objectでバインド対象のオブジェクトを指定します。
<form th:action="@{/submit}" th:object="${userForm}" method="post">
<input type="text" th:field="*{username}">
<input type="password" th:field="*{password}">
<button type="submit">送信</button>
</form>
次に、コントローラ側では@PostMappingで送信先を受け取り、@ModelAttributeを使ってフォームオブジェクトをバインドします。
@Controller
public class UserController {
@PostMapping("/submit")
public String handleForm(@ModelAttribute UserForm userForm) {
// userForm.getUsername()やgetPassword()で値を取得できる
return "result";
}
}
ここで重要なのが、Thymeleafのテンプレートで使っているモデル名(例:userForm)と、コントローラで受け取る@ModelAttributeの型が一致していることです。
さらに、バリデーションを使う場合は@ValidとBindingResultを組み合わせることで、エラーメッセージの表示や再描画にも対応できます。
@PostMapping("/submit")
public String handleForm(@Valid @ModelAttribute UserForm userForm, BindingResult result) {
if (result.hasErrors()) {
return "form"; // エラーがある場合は元のフォームに戻る
}
return "result"; // 正常なら次の画面へ
}
このように、Thymeleafのth:fieldでフォーム入力とJavaオブジェクトをバインドし、コントローラの@ModelAttributeで値を受け取る流れは、Spring MVCの基本的な連携パターンです。
初心者が間違えやすいのは、th:objectを使わずにth:fieldだけを使ってしまい、意図したバインドがされないというケースです。正しくオブジェクトと連携させるためにも、テンプレート・モデル・コントローラの対応を意識しておきましょう。