はじめに
Spring Bootで入力画面を作成するときは、次のような処理が必要になります。
- HTMLの入力値をJavaで受け取る
- POSTリクエストを処理する
- 未入力などの入力エラーを確認する
- エラーメッセージをHTMLへ表示する
- 登録後に一覧画面へ戻る
この記事では、Formクラスを使った入力値の受け取りと、バリデーションの基本的な書き方をまとめます。
特定のアプリに依存しないように、SampleFormという名前を使用します。
入力処理の基本的な流れ
画面から入力された値は、次のような流れで処理されます。
HTMLの入力フォーム
↓
POSTリクエスト
↓
Controller
↓
Formクラス
↓
Service
↓
登録・更新処理
Formクラスを作成する
Formクラスは、画面から入力された値を受け取るためのクラスです。
package com.example.sample.form; // 自分で作成
public class SampleForm { // 自分で作成
private String sampleName; // 自分で作成
public String getSampleName() {
return sampleName;
}
public void setSampleName(String sampleName) {
this.sampleName = sampleName;
}
}
次の名前は自分で決めます。
SampleForm
sampleName
getSampleName()
setSampleName()
フィールド名がsampleNameの場合、getterとsetterは次の名前にします。
getSampleName()
setSampleName()
HTMLの入力フォームを作成する
Thymeleafを使用して、FormクラスとHTMLを紐付けます。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>サンプル登録</title>
</head>
<body>
<form
th:action="@{/samples}"
th:object="${sampleForm}"
method="post">
<label for="sampleName">名前</label>
<input
id="sampleName"
type="text"
th:field="*{sampleName}">
<button type="submit">登録</button>
</form>
</body>
</html>
th:action
フォームの送信先URLを指定します。
th:action="@{/samples}"
この例では、入力内容を/samplesへ送信します。
method="post"
POSTリクエストとして送信します。
method="post"
th:object
フォームで使用するJavaオブジェクトを指定します。
th:object="${sampleForm}"
th:field
Formクラスのフィールドと入力欄を紐付けます。
th:field="*{sampleName}"
Formクラスのフィールド名と一致させます。
private String sampleName;
th:field="*{sampleName}"
入力画面を表示するController
入力画面を表示するときは、空のFormオブジェクトをModelへ登録します。
package com.example.sample.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.example.sample.form.SampleForm;
@Controller
public class SampleController {
@GetMapping("/samples/new")
public String showSampleForm(Model model) {
model.addAttribute(
"sampleForm",
new SampleForm()
);
return "sample-form";
}
}
HTMLのth:objectと、addAttribute()の属性名を一致させます。
Controller側です。
model.addAttribute("sampleForm", new SampleForm());
HTML側です。
th:object="${sampleForm}"
POSTリクエストで入力値を受け取る
画面から送信された値を、Controllerで受け取ります。
package com.example.sample.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.sample.form.SampleForm;
@Controller
public class SampleController {
@PostMapping("/samples")
public String registerSample(
@ModelAttribute("sampleForm")
SampleForm sampleForm) {
String sampleName =
sampleForm.getSampleName();
return "redirect:/samples";
}
}
@ModelAttributeは、送信された入力値をFormオブジェクトへ格納します。
@ModelAttribute("sampleForm")
SampleForm sampleForm
入力値はgetterで取得できます。
String sampleName =
sampleForm.getSampleName();
Serviceへ登録処理を渡す
Controllerで受け取った値を、Serviceへ渡します。
package com.example.sample.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.sample.form.SampleForm;
import com.example.sample.service.SampleService;
@Controller
public class SampleController {
private final SampleService sampleService;
public SampleController(
SampleService sampleService) {
this.sampleService = sampleService;
}
@PostMapping("/samples")
public String registerSample(
@ModelAttribute("sampleForm")
SampleForm sampleForm) {
sampleService.createSample(
sampleForm.getSampleName()
);
return "redirect:/samples";
}
}
Serviceの例です。
package com.example.sample.service;
import org.springframework.stereotype.Service;
@Service
public class SampleService {
public void createSample(String sampleName) {
// 登録処理などを記載する
System.out.println(sampleName);
}
}
@NotBlankで未入力をチェックする
Formクラスのフィールドに@NotBlankを付けます。
package com.example.sample.form;
import jakarta.validation.constraints.NotBlank;
public class SampleForm {
@NotBlank(message = "名前を入力してください")
private String sampleName;
public String getSampleName() {
return sampleName;
}
public void setSampleName(String sampleName) {
this.sampleName = sampleName;
}
}
@NotBlankは、次の値をエラーにします。
null- 空文字
- 空白だけの文字列
@NotBlankはJakartaが用意しています。
エラーメッセージは自分で決めます。
@NotBlank(message = "名前を入力してください")
Controllerでバリデーションを実行する
ControllerのForm引数に@Validatedを付けます。
package com.example.sample.controller;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.sample.form.SampleForm;
@Controller
public class SampleController {
@PostMapping("/samples")
public String registerSample(
@Validated
@ModelAttribute("sampleForm")
SampleForm sampleForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "sample-form";
}
return "redirect:/samples";
}
}
@Validated
Formに設定した入力チェックを実行します。
@Validated
BindingResult
入力チェックの結果を受け取ります。
BindingResult bindingResult
エラーがあるかどうかは、hasErrors()で確認します。
if (bindingResult.hasErrors()) {
return "sample-form";
}
BindingResultを書く位置
BindingResultは、入力チェックするFormの直後に書きます。
正しい例です。
@Validated
@ModelAttribute("sampleForm")
SampleForm sampleForm,
BindingResult bindingResult
FormとBindingResultの間に、別の引数を入れないようにします。
避ける例です。
@Validated
@ModelAttribute("sampleForm")
SampleForm sampleForm,
Model model,
BindingResult bindingResult
HTMLへエラーメッセージを表示する
th:errorsを使用して、Formに設定したエラーメッセージを表示します。
<form
th:action="@{/samples}"
th:object="${sampleForm}"
method="post">
<label for="sampleName">名前</label>
<input
id="sampleName"
type="text"
th:field="*{sampleName}">
<p th:errors="*{sampleName}"></p>
<button type="submit">登録</button>
</form>
Formクラスのフィールドです。
private String sampleName;
HTMLの入力欄です。
th:field="*{sampleName}"
HTMLのエラー表示です。
th:errors="*{sampleName}"
すべてsampleNameで一致させます。
入力処理の全体例
Form
package com.example.sample.form;
import jakarta.validation.constraints.NotBlank;
public class SampleForm {
@NotBlank(message = "名前を入力してください")
private String sampleName;
public String getSampleName() {
return sampleName;
}
public void setSampleName(String sampleName) {
this.sampleName = sampleName;
}
}
Controller
package com.example.sample.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.example.sample.form.SampleForm;
import com.example.sample.service.SampleService;
@Controller
public class SampleController {
private final SampleService sampleService;
public SampleController(
SampleService sampleService) {
this.sampleService = sampleService;
}
@GetMapping("/samples/new")
public String showSampleForm(Model model) {
model.addAttribute(
"sampleForm",
new SampleForm()
);
return "sample-form";
}
@PostMapping("/samples")
public String registerSample(
@Validated
@ModelAttribute("sampleForm")
SampleForm sampleForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "sample-form";
}
sampleService.createSample(
sampleForm.getSampleName()
);
return "redirect:/samples";
}
}
HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>サンプル登録</title>
</head>
<body>
<form
th:action="@{/samples}"
th:object="${sampleForm}"
method="post">
<label for="sampleName">名前</label>
<input
id="sampleName"
type="text"
th:field="*{sampleName}">
<p th:errors="*{sampleName}"></p>
<button type="submit">登録</button>
</form>
</body>
</html>
登録後にリダイレクトする
登録処理が完了したら、一覧画面へリダイレクトします。
return "redirect:/samples";
処理の流れです。
POSTで登録
↓
登録処理が完了
↓
/samplesへリダイレクト
↓
GETで一覧画面を表示
登録後にリダイレクトすることで、ブラウザを更新したときの二重登録を防ぎやすくなります。
入力チェックが動かないときの確認項目
入力チェックが動作しない場合は、次の内容を確認します。
- Formのフィールドに
@NotBlankなどが付いているか - ControllerのForm引数に
@Validatedが付いているか -
BindingResultがFormの直後にあるか - HTMLに
th:errorsがあるか - Validationの依存関係が追加されているか
必要な構成を簡単にまとめると、次のようになります。
Form
└─ @NotBlank
Controller
├─ @Validated
└─ BindingResult
HTML
└─ th:errors
まとめ
画面入力を受け取るときは、次の役割に分けます。
Form
→ 入力値を保持する
HTML
→ 入力欄とFormを紐付ける
Controller
→ POSTリクエストを受け取る
Service
→ 登録や更新の処理を行う
バリデーションでは、次の4つを使用します。
@NotBlank
@Validated
BindingResult
th:errors
次の記事では、外部APIの呼び出しと、データベースの基本操作についてまとめます。