Thymeleaf th:actionの使い方を完全ガイド!フォーム送信先を動的に設定する方法
新人
「Spring MVCでフォームを作ってたら、th:actionっていうのを見かけたんですが、これは何に使うんですか?」
先輩
「それはThymeleafでフォームの送信先URLを動的に指定するための属性だよ。Springのコントローラと連携するときにとても便利なんだ。」
新人
「HTMLのactionとは違うんですか?」
先輩
「それじゃあ、まずはHTMLの基本的なactionからおさらいして、Spring MVCとth:actionの関係も整理していこう!」
1. HTMLのform actionとは?
HTMLでフォームを作るとき、<form>タグのaction属性は、データの送信先URLを指定するために使います。たとえば、ユーザーの名前を送信するフォームなら次のように書きます。
<form action="/submit" method="post">
<input type="text" name="username" />
<button type="submit">送信</button>
</form>
このようにactionにURLを直接書くことで、どこにデータを送信するかが決まります。ただし、これだと送信先を動的に変更したり、パラメータを埋め込んだりするのが難しくなります。
2. Spring MVCにおけるコントローラとURLの関係
Spring MVCでは、@Controllerと@RequestMappingや@PostMappingなどを使って、URLと処理を結び付けます。たとえば、フォームの送信先として/registerというURLに対応する処理を以下のように記述できます。
@Controller
public class UserController {
@PostMapping("/register")
public String register(@RequestParam String username) {
// 登録処理
return "success";
}
}
HTMLでaction="/register"と書いて送信すれば、このコントローラのメソッドが呼び出されます。ですが、URLをハードコーディングしてしまうと、URL変更時の修正漏れや再利用のしづらさにつながります。
3. Thymeleafのth:actionとは何か?
Thymeleafでは、HTMLのaction属性の代わりに、th:actionを使ってフォーム送信先を動的に指定できます。Spring MVCのコントローラのURLと連携して、安全かつ柔軟な記述が可能です。
<form th:action="@{/register}" method="post">
<input type="text" name="username" />
<button type="submit">登録</button>
</form>
このth:action="@{/register}"は、Spring MVCのルーティングを意識した記述であり、プロジェクトの構成変更にも強く、URLの整合性を保ちやすくなります。また、@{}の中でパラメータを渡すことも可能なので、柔軟にURLを組み立てることができます。
静的なHTMLではできなかった「URLの動的生成」や「Springとのバインド」を実現できるのがth:actionの最大の特徴です。
4. th:actionで動的にURLを生成する基本的な書き方
th:actionの基本的な書き方では、@{}の構文を使用して、URLをSpringのルーティングに合わせて動的に組み立てます。これにより、URLの変更やパラメータの追加にも柔軟に対応できるようになります。
<form th:action="@{/submit}" method="post">
<input type="text" name="email" />
<button type="submit">送信</button>
</form>
このように記述すると、action属性には/submitというURLが動的に挿入されます。Spring MVCのコントローラ側でこのURLに対応するメソッドがあれば、フォーム送信時にそのメソッドが呼び出されます。
静的にaction="/submit"と書くのではなく、Thymeleafのth:action="@{/submit}"を使うことで、開発中のURL変更やルート設計の修正にも強くなり、保守性が高まります。
5. URLにパラメータを含める方法(パス変数やクエリパラメータ)
フォーム送信先のURLに動的な値を含めたい場合、th:actionではパス変数やクエリパラメータを使って簡単に埋め込むことができます。たとえば、ユーザーIDごとに送信先を変えるケースを考えてみましょう。
<form th:action="@{/user/{id}/edit(id=${user.id})}" method="post">
<input type="text" name="username" />
<button type="submit">更新</button>
</form>
ここでは@{/user/{id}/edit(id=${user.id})}という形で、{id}というパス変数に対して、user.idの値を代入しています。これにより、たとえばユーザーIDが5の場合、最終的な送信先は/user/5/editになります。
また、クエリパラメータを使いたい場合も、同様にth:actionの中で指定可能です。
<form th:action="@{/search(keyword=${keyword})}" method="get">
<input type="text" name="keyword" th:value="${keyword}" />
<button type="submit">検索</button>
</form>
この例では、URLに?keyword=入力値という形でクエリパラメータを追加できます。こうした書き方により、パス変数もクエリパラメータも簡単に扱えるのがth:actionの魅力です。
6. コントローラ側のマッピングとの対応関係を具体例で紹介
ここまででth:actionを使ったフォーム送信の記述方法を見てきましたが、Spring MVCではコントローラ側のURLマッピングと正しく対応していなければ、リクエストは正しく処理されません。ここで、th:actionと@Controllerの連携例を確認してみましょう。
@Controller
public class UserController {
@GetMapping("/user/{id}/edit")
public String editForm(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "editForm";
}
@PostMapping("/user/{id}/edit")
public String updateUser(@PathVariable Long id, @RequestParam String username) {
userService.updateUsername(id, username);
return "redirect:/user/" + id;
}
}
このようにコントローラ側で@PathVariableを使ってIDを受け取り、GETでフォーム画面を表示し、POSTで送信された値を処理する構成にしておけば、th:actionの中で/user/{id}/editというURLを指定することができます。
<form th:action="@{/user/{id}/edit(id=${user.id})}" method="post">
<input type="text" name="username" th:value="${user.username}" />
<button type="submit">保存</button>
</form>
これにより、ThymeleafのフォームからSpringのコントローラにデータを正しく送信できるようになります。th:actionはURLの動的生成に強く、@PathVariableや@RequestParamと組み合わせることで、柔軟かつ安全なフォーム処理を実現できます。
さらに、コントローラ側でModelにデータを追加しておくことで、画面表示にも利用できるため、フォームの初期値表示にも対応できます。
7. よくあるエラーと注意点(null、URL解決失敗、th:actionの書き間違い)
th:actionを使うと便利な反面、初心者がつまずきやすいポイントもいくつかあります。ここでは代表的なエラーとその対処法を見ていきましょう。
まず多いのが、「th:actionのURLに埋め込む変数がnull」になっているケースです。たとえば以下のように書いている場合、
<form th:action="@{/user/{id}/edit(id=${user.id})}" method="post">
user.idがnullだった場合、URLが正しく生成されず、/user/null/editのようになってしまいます。このような場合、URLが無効になるか、そもそもページの表示時にエラーになります。
このエラーを防ぐには、コントローラ側でuserが必ずModelに渡っているか、nullチェックをしておくことが大切です。
次に多いのが、「URLの書き間違い」です。たとえば@{/user{id}/edit}のように{}の記法を間違えると、Thymeleafがうまく解釈できず、ビルドは通っても実行時にエラーになります。
また、URLに使う変数名と、th:actionの中の指定がずれていると、値がバインドされずURL生成に失敗します。変数名が正しいか、スペルミスがないかを丁寧に確認しましょう。
特に注意すべき点は、「URLの解決に失敗する場合は、ThymeleafのHTML自体が表示されない」ということです。静的HTMLのように「途中まで表示される」のではなく、完全にエラー画面になることもありますので、注意が必要です。
8. th:actionとth:methodの使い分け
th:actionとセットでよく使うのがth:methodです。これはフォームのHTTPメソッド(GETやPOSTなど)を指定するための属性で、HTMLのmethodと同じ役割を果たします。
HTMLでは次のように書きます。
<form action="/login" method="post">
Thymeleafではこれを動的にしたい場合、th:actionとth:methodの両方を使って、以下のように記述します。
<form th:action="@{/login}" th:method="post">
実は、method属性はth:methodを使わずに静的に書いても動作しますが、テンプレート全体をThymeleafで統一したい場合や、フォームを再利用する場面ではth:methodの使用が推奨されます。
また、th:methodではgetとpost以外にも、putやdeleteといったHTTPメソッドも指定できます。ただし、これらはHTMLフォームでは直接対応していないため、Spring MVCでは_methodという隠しフィールドを自動で追加して対応します。
たとえば、次のような記述でPUTメソッドのリクエストを送ることが可能です。
<form th:action="@{/user/update}" th:method="put">
このように、HTTPメソッドも含めてThymeleafで柔軟に設定できる点は、Springとの統合を強く意識した開発において大きなメリットとなります。
9. 実務で役立つ応用例と開発Tips(複数フォーム、リスト表示+個別送信など)
最後に、実際の現場でよく使われるth:actionの応用テクニックを紹介します。まずは、1画面に複数のフォームが存在するケースです。
たとえば、1ページに「ログインフォーム」と「新規登録フォーム」が同時にある場合、それぞれ送信先が異なる必要があります。そんなときは、次のようにフォームごとにth:actionを分けて記述します。
<form th:action="@{/login}" method="post">
<!-- ログイン用 -->
</form>
<form th:action="@{/register}" method="post">
<!-- 新規登録用 -->
</form>
このように分けることで、それぞれのフォームが異なるURLにデータを送信でき、処理を分岐させやすくなります。
次に、リスト表示と個別送信を組み合わせた応用例です。たとえば、商品リストを表示しつつ、「この商品だけ注文する」というボタンを設置したい場合、各行にフォームを設けて送信先URLに商品IDを組み込みます。
<tr th:each="item : ${items}">
<td th:text="${item.name}">商品名</td>
<td>
<form th:action="@{/order/{id}(id=${item.id})}" method="post">
<button type="submit">注文</button>
</form>
</td>
</tr>
このように書くことで、リストの中の各商品に対して個別に送信できるフォームを用意できます。これはECサイトや管理画面などでもよく使われるパターンです。
応用例をうまく使いこなすためには、「th:actionはURLを動的に組み立てるための仕組みである」という基本をしっかり理解することが大切です。
また、Springの@Controllerと@PathVariableや@RequestParamを組み合わせることで、フォーム処理は柔軟かつ安全に行えるようになります。
初心者の方でも、今回紹介したパターンをベースにすれば、さまざまな画面で実践的なフォーム処理が実装できるようになります。慣れてきたら、th:ifやth:eachなどと組み合わせて、よりダイナミックな画面作成にも挑戦してみてください。