Spring Bootのフォームクラスでリクエストパラメータを送信・受け取る方法を新人エンジニア向けに解説
こんにちは。ゆうせいです。
今回は、Spring Bootで「フォームクラスを使ってリクエストパラメータを扱う方法」を、新人エンジニア向けに解説します。
最初に大切なポイントを整理します。
正確に言うと、フォームクラスがリクエストパラメータを送信するわけではありません。
リクエストパラメータを送信するのはHTMLフォームです。
そして、Spring Boot側でそのリクエストパラメータを受け取るための入れ物がフォームクラスです。
たとえるなら、HTMLフォームは「申込用紙」です。
ユーザーが申込用紙に名前や価格を入力して送信します。
フォームクラスは、Spring Boot側でその申込内容を受け取る「専用の箱」です。
Spring MVCでは、@RequestParamを使うとクエリパラメータやフォームデータをControllerの引数にバインドできます。また、@ModelAttributeを使うと、リクエストパラメータをオブジェクトにまとめてバインドできます。Spring公式ドキュメントでも、@RequestParamはクエリパラメータやフォームデータをメソッド引数にバインドできること、@ModelAttributeではデフォルトでコンストラクターバインディングとプロパティデータバインディングが適用されることが説明されています。
フォームクラスとは何か
フォームクラスとは、画面から送信された入力値を受け取るためのJavaクラスです。
たとえば、車を検索する画面があるとします。
検索画面には、次の入力欄があるとします。
| 入力欄 | 意味 |
|---|---|
| name | 車名 |
| minPrice | 最低価格 |
| maxPrice | 最高価格 |
この3つの値を1つずつ@RequestParamで受け取ることもできます。
@PostMapping("/cars/search")
public String search(
@RequestParam String name,
@RequestParam Integer minPrice,
@RequestParam Integer maxPrice) {
// 検索処理
return "cars/search";
}ただし、項目が増えるとControllerの引数がどんどん長くなります。
そこで、入力値をまとめて受け取るためにフォームクラスを作ります。
public class CarSearchForm {
private String name;
private Integer minPrice;
private Integer maxPrice;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getMinPrice() {
return minPrice;
}
public void setMinPrice(Integer minPrice) {
this.minPrice = minPrice;
}
public Integer getMaxPrice() {
return maxPrice;
}
public void setMaxPrice(Integer maxPrice) {
this.maxPrice = maxPrice;
}
@Override
public String toString() {
return "CarSearchForm{" +
"name='" + name + '\'' +
", minPrice=" + minPrice +
", maxPrice=" + maxPrice +
'}';
}
}このCarSearchFormが、画面から送られてきた検索条件を受け取る箱になります。
DTOと似ていますが、フォームクラスは「画面入力を受け取るためのクラス」と考えるとわかりやすいです。
フォームクラスを使う全体の流れ
フォームクラスを使った送信処理は、次の流れになります。
| 順番 | 場所 | 処理 |
|---|---|---|
| 1 | Controller | 画面表示用に空のフォームクラスをModelへ入れる |
| 2 | HTML | フォームクラスのフィールドに対応する入力欄を作る |
| 3 | ブラウザ | ユーザーが入力して送信する |
| 4 | Spring Boot | リクエストパラメータをフォームクラスに自動でセットする |
| 5 | Controller | フォームクラスから値を取り出して処理する |
たとえるなら、画面がアンケート用紙です。
ユーザーがアンケートに回答します。
Spring Bootは、回答内容をCarSearchFormという箱に自動で入れてくれます。
新人エンジニアがまず覚えるべきポイントは、HTMLのname属性とフォームクラスのフィールド名が対応することです。
Controllerを作る
まず、Controllerを作ります。
今回は、車検索画面を表示し、検索条件を受け取るサンプルにします。
package com.example.demo.controller;
import com.example.demo.form.CarSearchForm;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class CarController {
@GetMapping("/cars/search")
public String showSearchForm(Model model) {
CarSearchForm form = new CarSearchForm();
model.addAttribute("carSearchForm", form);
return "cars/search";
}
@PostMapping("/cars/search")
public String searchCars(
@ModelAttribute CarSearchForm carSearchForm,
Model model) {
System.out.println("受け取った検索条件: " + carSearchForm);
model.addAttribute("carSearchForm", carSearchForm);
model.addAttribute("message",
"車名: " + carSearchForm.getName()
+ "、最低価格: " + carSearchForm.getMinPrice()
+ "、最高価格: " + carSearchForm.getMaxPrice());
return "cars/search";
}
}@Controllerは、画面を返すControllerを作るためのアノテーションです。
@GetMapping("/cars/search")は、検索画面を表示する処理です。
@PostMapping("/cars/search")は、フォーム送信後の処理です。
@ModelAttribute CarSearchForm carSearchFormと書くことで、送信されたリクエストパラメータがCarSearchFormに自動でセットされます。
Spring公式ドキュメントでは、@ModelAttributeを使った引数ではデータバインディングが適用されると説明されています。また、セキュリティ上の理由から、Webバインディング専用にカスタマイズされたオブジェクトを使うことが推奨されています。つまり、DBのEntityを直接フォーム受け取りに使うより、今回のような専用フォームクラスを作るほうが安全な設計になりやすいです。
HTMLフォームを作る
次に、Thymeleafを使ったHTMLフォームを作ります。
ファイルの場所は、次のようなイメージです。
src/main/resources/templates/cars/search.html
コードは次のとおりです。
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>車検索</title>
</head>
<body>
<h1>車検索</h1>
<form th:action="@{/cars/search}" th:object="${carSearchForm}" method="post">
<div>
<label for="name">車名</label>
<input type="text" th:field="*{name}" id="name">
</div>
<div>
<label for="minPrice">最低価格</label>
<input type="number" th:field="*{minPrice}" id="minPrice">
</div>
<div>
<label for="maxPrice">最高価格</label>
<input type="number" th:field="*{maxPrice}" id="maxPrice">
</div>
<button type="submit">検索する</button>
</form>
<p th:text="${message}"></p>
</body>
</html>ここで大事なのは、次の2つです。
| Thymeleafの書き方 | 意味 |
|---|---|
| th:object="${carSearchForm}" | このフォームはcarSearchFormを使うという宣言 |
| th:field="*{name}" | carSearchFormのnameフィールドと入力欄を結びつける |
th:field="*{name}"を書くと、HTML上ではname="name"のような属性が生成されます。
Spring Bootは、送信されたnameというリクエストパラメータを見て、CarSearchFormのsetNameメソッドを呼びます。
つまり、次のように対応します。
| HTML側 | フォームクラス側 |
|---|---|
| name="name" | setName(String name) |
| name="minPrice" | setMinPrice(Integer minPrice) |
| name="maxPrice" | setMaxPrice(Integer maxPrice) |
Thymeleafを使わない普通のHTMLでも送信できる
フォームクラスを使うために、必ずThymeleafが必要というわけではありません。
普通のHTMLでも、name属性がフォームクラスのフィールド名と一致していれば、Spring Boot側で受け取れます。
<form action="/cars/search" method="post">
<div>
<label for="name">車名</label>
<input type="text" id="name" name="name">
</div>
<div>
<label for="minPrice">最低価格</label>
<input type="number" id="minPrice" name="minPrice">
</div>
<div>
<label for="maxPrice">最高価格</label>
<input type="number" id="maxPrice" name="maxPrice">
</div>
<button type="submit">検索する</button>
</form>このHTMLから送信されるリクエストパラメータは、次のような形になります。
name=プリウス
minPrice=1000000
maxPrice=3000000
Spring Bootは、このパラメータ名を見て、CarSearchFormへ値を入れます。
郵便でたとえるなら、name、minPrice、maxPriceという宛名ラベルが貼られた荷物を、Spring Bootが対応する箱に入れてくれるようなものです。
フォームクラスに値が入る仕組み
フォーム送信時、ブラウザは次のようなデータをSpring Bootへ送ります。
name=プリウス&minPrice=1000000&maxPrice=3000000
Spring Bootは、CarSearchFormのフィールド名とリクエストパラメータ名を照合します。
| リクエストパラメータ | 呼ばれるsetter | セットされる値 |
|---|---|---|
| name | setName | プリウス |
| minPrice | setMinPrice | 1000000 |
| maxPrice | setMaxPrice | 3000000 |
この「リクエストパラメータをJavaオブジェクトへ入れる処理」をバインドといいます。
バインドとは、ひもづけるという意味です。
スマホとBluetoothイヤホンをペアリングするように、画面の入力値とJavaのフィールドをひもづけるイメージです。
@RequestParamとの違い
フォームクラスを使わずに、@RequestParamで1つずつ受け取ることもできます。
@PostMapping("/cars/search")
public String searchCars(
@RequestParam String name,
@RequestParam Integer minPrice,
@RequestParam Integer maxPrice,
Model model) {
return "cars/search";
}この方法も間違いではありません。
Spring公式ドキュメントでも、@RequestParamはクエリパラメータやフォームデータをControllerのメソッド引数にバインドできると説明されています。
ただし、入力項目が多くなるとControllerが読みにくくなります。
| 比較 | @RequestParam | フォームクラス |
|---|---|---|
| 向いている場面 | 項目が少ない | 項目が複数ある |
| Controllerの見た目 | 引数が増えやすい | フォームクラス1つで受け取れる |
| 入力値の管理 | バラバラになりやすい | 1つのクラスにまとまる |
| バリデーション | 個別に書きがち | フォームクラスにまとめやすい |
検索条件が1つだけなら@RequestParamで十分です。
検索条件が3つ、5つ、10個と増えるなら、フォームクラスを使うほうが整理しやすいです。
GETでもフォームクラスは使える
検索画面では、method="get"を使うこともあります。
GET送信では、入力値がURLに付きます。
/cars/search?name=プリウス&minPrice=1000000&maxPrice=3000000
GETでも、フォームクラスで受け取れます。
@GetMapping("/cars/search/result")
public String searchCarsByGet(
@ModelAttribute CarSearchForm carSearchForm,
Model model) {
System.out.println("GETで受け取った検索条件: " + carSearchForm);
model.addAttribute("carSearchForm", carSearchForm);
return "cars/search";
}HTMLは次のようになります。
<form th:action="@{/cars/search/result}" th:object="${carSearchForm}" method="get">
<input type="text" th:field="*{name}">
<input type="number" th:field="*{minPrice}">
<input type="number" th:field="*{maxPrice}">
<button type="submit">検索する</button>
</form>GETは検索条件に向いています。
なぜなら、URLをコピーすれば、同じ検索条件を他の人に共有しやすいからです。
一方で、パスワードや個人情報のような見せたくない値をGETで送るのは避けましょう。
POSTとGETの使い分け
| 比較 | GET | POST |
|---|---|---|
| 値の見え方 | URLに表示される | URLには表示されにくい |
| 向いている処理 | 検索、絞り込み、一覧表示 | 登録、更新、削除 |
| ブラウザの戻る操作 | 比較的扱いやすい | 再送信確認が出ることがある |
| 例 | 車名で検索する | 車情報を登録する |
たとえるなら、GETは「検索メモを見える場所に貼る」イメージです。
POSTは「申込書を封筒に入れて提出する」イメージです。
検索ならGET、登録や更新ならPOSTと覚えるとよいです。
車登録フォームのサンプル
検索だけでなく、登録フォームでもフォームクラスを使えます。
次は、車情報を登録する例です。
CarRegisterFormクラス
package com.example.demo.form;
public class CarRegisterForm {
private String name;
private Integer price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
@Override
public String toString() {
return "CarRegisterForm{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}Controller
package com.example.demo.controller;
import com.example.demo.form.CarRegisterForm;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class CarRegisterController {
@GetMapping("/cars/new")
public String showRegisterForm(Model model) {
model.addAttribute("carRegisterForm", new CarRegisterForm());
return "cars/new";
}
@PostMapping("/cars/new")
public String registerCar(
@ModelAttribute CarRegisterForm carRegisterForm,
Model model) {
System.out.println("登録フォームの内容: " + carRegisterForm);
model.addAttribute("message",
carRegisterForm.getName() + "を登録しました。価格は"
+ carRegisterForm.getPrice() + "円です。");
return "cars/new";
}
}HTML
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>車登録</title>
</head>
<body>
<h1>車登録</h1>
<form th:action="@{/cars/new}" th:object="${carRegisterForm}" method="post">
<div>
<label for="name">車名</label>
<input type="text" th:field="*{name}" id="name">
</div>
<div>
<label for="price">価格</label>
<input type="number" th:field="*{price}" id="price">
</div>
<button type="submit">登録する</button>
</form>
<p th:text="${message}"></p>
</body>
</html>このフォームで「プリウス」「2500000」と入力して送信すると、Spring Boot側では次のようなCarRegisterFormが作られます。
CarRegisterForm{name='プリウス', price=2500000}
このように、フォームクラスを使うと、画面入力をJavaのオブジェクトとして扱えます。
フォームクラスをDAOへ渡すべきか
新人エンジニアが迷いやすい点として、「フォームクラスをそのままDAOへ渡してよいのか?」があります。
学習用の小さなサンプルなら、フォームクラスをDAOへ渡しても動きます。
ただし、実務では次のように役割を分けることが多いです。
| クラス | 役割 |
|---|---|
| Form | 画面から送られた入力値を受け取る |
| DTO | アプリ内部やDB処理に必要なデータを運ぶ |
| DAO | SQLを実行してDBにアクセスする |
| Service | 業務処理の流れを組み立てる |
たとえば、登録処理では次のようにFormからDTOへ詰め替えることがあります。
CarDto carDto = new CarDto();
carDto.setName(carRegisterForm.getName());
carDto.setPrice(carRegisterForm.getPrice());
carsDao.insertCar(carDto);Formは画面用の入れ物です。
DTOは処理用の入れ物です。
お弁当でたとえるなら、Formはお店の注文用紙です。
DTOは厨房に渡す調理指示書です。
似ていますが、使う場所が違います。
Integerを使う理由
フォームクラスでは、価格をintではなくIntegerにしています。
private Integer minPrice;
private Integer maxPrice;理由は、入力欄が空の可能性があるからです。
intは基本型なので、nullを持てません。
Integerは参照型なので、未入力をnullとして扱えます。
| 型 | nullを持てるか | フォーム入力との相性 |
|---|---|---|
| int | 持てない | 未入力を扱いにくい |
| Integer | 持てる | 未入力をnullで表せる |
検索フォームでは、最低価格や最高価格を空にすることがあります。
そのため、Integerを使うと扱いやすいです。
よくあるエラー1:フォームクラスにsetterがない
フォームクラスにsetterがないと、Spring Bootが値をセットできません。
悪い例です。
public class CarSearchForm {
private String name;
public String getName() {
return name;
}
}このクラスにはsetNameがありません。
そのため、リクエストパラメータnameの値を入れられません。
良い例です。
public class CarSearchForm {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}フォームクラスには、基本的にgetterとsetterを用意しましょう。
よくあるエラー2:HTMLのname属性とフィールド名が違う
フォームクラスのフィールド名がnameなのに、HTML側がcarNameになっていると、自動で入りません。
フォームクラスです。
private String name;悪いHTML例です。
<input type="text" name="carName">この場合、Spring BootはCarSearchFormのnameに値を入れられません。
良いHTML例です。
<input type="text" name="name">Thymeleafを使うなら、次のように書くと対応関係を間違えにくくなります。
<input type="text" th:field="*{name}">よくあるエラー3:Modelにフォームオブジェクトを入れていない
Thymeleafでth:object="${carSearchForm}"を使う場合、ControllerでcarSearchFormをModelに入れておく必要があります。
悪い例です。
@GetMapping("/cars/search")
public String showSearchForm() {
return "cars/search";
}この場合、HTML側でcarSearchFormが見つからない可能性があります。
良い例です。
@GetMapping("/cars/search")
public String showSearchForm(Model model) {
model.addAttribute("carSearchForm", new CarSearchForm());
return "cars/search";
}画面を表示する前に、空のフォームオブジェクトを用意しておくのが基本です。
よくあるエラー4:数値に変換できない値が送られる
フォームクラスでIntegerを使っている場合、Spring Bootは送信された文字列をIntegerに変換しようとします。
たとえば、次のような値なら変換できます。
2500000
しかし、次のような値はIntegerに変換できません。
abc
Spring公式ドキュメントでも、@RequestParamでString以外の型を使う場合には型変換が適用されると説明されています。フォームクラスでも同じように、数値型へ変換できない値が来るとエラーになる可能性があります。
実務では、入力チェックを入れます。
入力チェックは、次の学習テーマであるバリデーションにつながります。
フォームクラスを使うメリット
| メリット | 内容 |
|---|---|
| Controllerが読みやすくなる | 入力項目を1つのクラスにまとめられる |
| 入力値を管理しやすい | 画面ごとの入力項目が明確になる |
| バリデーションを追加しやすい | フォームクラスに入力チェックルールをまとめられる |
| 画面との対応がわかりやすい | HTMLの入力欄とJavaのフィールドが対応する |
| Entityを直接使わずに済む | Web入力専用の安全な入れ物を作れる |
特に重要なのは、Entityを直接使わないことです。
EntityはDBテーブルに近いクラスです。
画面から送信された値をEntityに直接バインドすると、意図しないフィールドまで書き換えられる危険があります。
Spring公式ドキュメントでも、Webバインディング専用にカスタマイズされたオブジェクトを使うことが推奨されています。
フォームクラスを使うデメリット
| デメリット | 内容 |
|---|---|
| クラスが増える | 画面ごとにFormクラスを作るためファイル数が増える |
| DTOへの詰め替えが必要になることがある | FormとDTOを分ける場合、変換処理が必要 |
| 命名を間違えると値が入らない | HTMLのname属性とフィールド名の対応が重要 |
| 型変換エラーに注意が必要 | 数値欄に文字列が来るとエラーになる場合がある |
ただし、クラスが増えるデメリットより、入力値を安全に整理できるメリットのほうが大きいです。
学校のプリントを教科ごとに分けると、ファイルは増えます。
でも、数学、英語、理科が混ざったプリントの山より、ずっと探しやすくなりますよね。
フォームクラスも同じです。
新人エンジニア向けの実装手順
フォームクラスを使った送信処理を作るときは、次の順番で進めると迷いにくいです。
| 順番 | 作業 |
|---|---|
| 1 | 画面で入力する項目を決める |
| 2 | 入力項目に対応するフォームクラスを作る |
| 3 | getterとsetterを作る |
| 4 | GETメソッドで空のフォームクラスをModelに入れる |
| 5 | HTMLフォームを作る |
| 6 | POSTメソッドで@ModelAttributeを使って受け取る |
| 7 | 受け取った値をログや画面で確認する |
| 8 | 必要ならServiceやDAOへ渡す |
最初からDB登録まで一気に作ると混乱しやすいです。
まずはControllerで値を受け取れているか確認しましょう。
System.out.println(carRegisterForm);この確認ができてから、ServiceやDAOに進むのがおすすめです。
まとめ
Spring Bootのフォームクラスとは、HTMLフォームから送信されたリクエストパラメータをまとめて受け取るためのJavaクラスです。
送信するのはHTMLフォームで、受け取るのがフォームクラスです。
| ポイント | 内容 |
|---|---|
| フォームクラスの役割 | 画面入力を受け取る専用の入れ物 |
| 受け取り方 | @ModelAttributeを使ってControllerで受け取る |
| HTML側の重要点 | name属性またはth:fieldをフォームクラスのフィールド名に合わせる |
| @RequestParamとの違い | 単独項目なら@RequestParam、複数項目ならフォームクラスが便利 |
| 注意点 | getter、setter、Modelへの追加、型変換エラーに注意する |
一言でまとめるなら、フォームクラスは「画面から送られてきた入力値を、Spring Boot側でまとめて受け取る箱」です。
新人エンジニアは、まずCarRegisterFormやCarSearchFormのような小さなフォームクラスを作り、HTMLのname属性とJavaのフィールド名が対応する感覚をつかんでください。
今後の学習では、@RequestParam、@ModelAttribute、Thymeleafのth:objectとth:field、GETとPOSTの違い、入力チェックであるバリデーション、ServiceとDAOへの受け渡しを順番に学ぶとよいです。まずは「入力欄2つのフォームを作り、フォームクラスで受け取って画面に表示する」ところから始めてください!
セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。
投稿者プロフィール


