(第10回) Spring Boot2で登録フォームのバリデーション機能を追加【Spring Boot2で日記ウェブアプリ】

Spring Boot2.7で日記投稿ウェブアプリ入門の第10回です。日記アプリの新規登録処理にバリデーション機能(検証チェック)を追加します。Bean ValidationとSpringのバリデーション機能についても解説しています。Spring Bootの初心者・入門者の方は、参考にしてみてください。

※ 本ページはプロモーションが含まれています。

動作環境と今回の目的

最終更新日:2022/11/25

前回は、日記ウェブアプリのコントローラクラスに日記の新規登録処理をするメソッドを追加しました。
(第9回) 日記の新規登録機能を追加、CRUDのC(Create、作成)【Spring Boot2で日記ウェブアプリ】

これで日記データの新規投稿処理ができるようになりましたが、今のままだと新規投稿フォームで何も入力しなくても登録処理ができて空データが作成されてしまいます。

ですので今回は、日記の新規登録フォームにバリデーション機能(検証チェック)を追加して、空データは登録できないようにしていきます。

◾️動作環境やバージョンは以下の通りです。
OS:macOS Big Sur(バージョン11.7.1)
開発環境:Eclipse(Pleiades All in One、4.16(2020-06)、Java Full Edition版)
Spring Bootバージョン:2.7.5
->2.4でも確認済み
Java:11
データベース:H2
Bootstrap5.2.2

本記事は私が自分で調べたり実際にアプリを作りながら私独自の解釈に基づいて書いています。内容や解釈が間違っていたら申し訳ないのですが、そのあたりは大目に見てもらえればと思います。

今回作成するファイル

今回作成するファイルを先に書いておきます。

新規に作成するファイルは「NewDiaryForm.java」の1つで、編集するファイルは「DiaryController.java」「summary.html」の2つになります。

Spring Boot2でバリデーション機能を有効にする、Bean Validation

Spring Bootでバリデーション機能を開発する場合、JavaのBean Validation(javax.validation.constraintsパッケージ)やHibernateのバリデーション(org.hibernate.validator.constraintsパッケージ)を使用してバリデーションチェックのルール(検証ルール)を作成し、Springのバリデーション機能も活用していくことになります。

Spring Boot2で、Bean Validation(javax.validation.constraintsパッケージ)やHibernateのバリデーション(org.hibernate.validator.constraintsパッケージ)を使用する場合、プロジェクト作成時にライブラリの依存関係で「I/O->検証」にチェックを入れて、プロジェクトを作成する必要があります。

この日記投稿ウェブアプリ講座の第1回のページのプロジェクト作成時では「検証」にチェックを入れてプロジェクトを作成したので、2つのパッケージのクラスは問題なく使えるはずです。

もしもライブラリの依存関係で「検証」にチェックを入れずにプロジェクトを作成した場合、EclipseでSpring Bootプロジェクトを右クリック->"Spring"->"スターターの編集"を選択すれば、ライブラリの依存関係の選択画面が開くので、そこで検証にチェックを入れ直せば良いです。

または、直接pom.xmlファイルを編集します。dependenciesタグ直下にdependencyタグを追加すればバリデーション機能を使えるようになるはずです。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Spring Bootのバージョン2.2までなら、ライブラリ依存関係の「検証」がなくても「Spring Web」を選択していれば、上記のバリーデーション用のパッケージを使うことができます。

日記投稿ウェブアプリのバリデーションルール

まずは、今回の日記投稿ウェブアプリの新規登録時のバリデーションルールを先に考えておきます。

今回は新規投稿フォームで登録するものは日記本文だけですので、チェックするのも日記本文だけです。
そして、日記本文のチェック項目は、空文字禁止、文字数制限が3〜150文字という事にします。

フォームクラスの作成、バリデーションルールの設定

それでは画面の新規登録フォームに合わせてフォームクラスNewDiaryForm.javaを新規に作成します。そして、このフォームクラスに実際に検証したいバリデーションルールを書いていきます。

com.example.demoパッケージ内にNewDiaryForm.javaを作成して編集します。

package com.example.demo;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class NewDiaryForm {
  //新規投稿された日記本文
  @NotNull
  @Size(min=3, max=150)
  private String newdiary;
}
NewDiaryFormクラスにあるフィールドはnewdiaryだけです。newdiaryは新規投稿フォームの日記本文に合わせています。

ですのでこのフィールド名のnewdiaryは、新規投稿フォームの日記本文のinputタグのname属性に合わせる必要があります。第7回のページから、フロント側のsummary.htmlの一部を抜粋して確認してみます。

<h3>新規投稿</h3>
<form  th:action="@{/diary/add}" method="post">
<input type="text" name="newdiary" class="form-control mb-2">
<button type="submit" class="btn btn-primary">投稿する</button>
</form>

そして、フィールドnewdiaryには@NotNullと@Size(min=3, max=150)の2つのアノテーションを付与しています。この2つは両方ともJavaのBean Validation(Jakarta Bean Validation API)のアノテーションです。

日記本文のチェック項目は、空文字禁止、文字数制限が3〜150文字にすると上で書きましたが、@NotNullでNull禁止(空文字禁止)、@Sizeで文字数制限が3~150文字というチェックルールを意味しています。

◾️エラーメッセージの変更

バリデーションチェックでエラーになった場合、画面側でエラーメッセージが表示することができますが、Spring Bootではエラーメッセージの文言を指定しなくても、デフォルトで適当な文言を表示してくれます。

ただ、もちろんエラーメッセージの文言を自分で指定する事もできます。自分で指定する場合、バリデーションチェックのアノテーションにmessage属性を付けます。

@Size(min=3, max=150, message="文字数は3〜150文字です。")
private String newdiary;

また、別でメッセージ用のファイルを作ってメッセージ管理をする方法もあるというか、その方法の方が一般的かもしれません。このあたりのメッセージ管理をどうするかは、個人の考え方やプロジェクト方針によると思います。

(広告)AmazonでSpring Boot3(バージョン3系)の初心者向け入門書を探す!本でSpring Bootプログラミング開発を体系的に勉強する!

コントローラクラスの変更(新規登録メソッド、日記一覧表示メソッド)

続いて、コントローラクラスDiaryController.javaです。バリデーション機能を追加したDiaryController.javaを示します。

package com.example.demo;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("diary")
public class DiaryController {
  @Autowired
  DiaryRepository diaryRepository;
  
  //日記一覧情報の取得
  @GetMapping("summary")
  public String summary(Model model, NewDiaryForm newDiaryForm) {
    Iterable<Diary> diarys = diaryRepository.findAll();
    model.addAttribute("diarys", diarys);
    return "summary";
  }

  //指定されたidの日記を削除する
  @PostMapping("delete")
  public String delete(@RequestParam Integer id) {
    diaryRepository.deleteById(id);
    return "redirect:/diary/summary";
  }

  //@Validを@Validatedに変更しても問題なく動作します
  //日記の新規登録
  @PostMapping("add")
  public String add(Model model, @Valid NewDiaryForm newDiaryForm, BindingResult bindingResult) {
    //バリデーションエラーがあるかどうかチェックして、エラーがあるなら新規登録処理をせずに、一覧表示メソッドを呼び出す
    if (bindingResult.hasErrors()) {
    	return summary(model, newDiaryForm);
    }
    //ChronoUnit.SECONDSで秒以下を切り捨て
    Diary diary = new Diary(newDiaryForm.getNewdiary(), LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
    diaryRepository.save(diary);
    return "redirect:/diary/summary";
  }
}

前回(第9回)のページで作成したDiaryController.javaのソースと比較して変更したメソッドは2箇所で、日記の新規登録処理をするaddメソッドと日記一覧表示をするsummaryメソッドの定義部分です。

summaryメソッドの変更箇所は、引数にフォームクラスNewDiaryFormを追加しただけです。
◾️変更前

public String summary(Model model)
◾️変更後
public String summary(Model model, NewDiaryForm newDiaryForm)

これは、バリデーションエラー時にエラーメッセージを表示するために、フロント側のHTML(summary.html)でフォームクラスのnewDiaryFormを使うようになったからです(summary.htmlの修正については、コントローラクラスのあとに説明します)。

summaryメソッドの引数に"NewDiaryForm newDiaryForm"を追加すると、summaryメソッドが呼ばれた時にmodelにnewDiaryFormオブジェクトが自動でセットされ、そのままmodelがnewDiaryFormオブジェクトをフロント側へ返すという流れになるので、フロント側(HTML側)でnewDiaryFormオブジェクトが使えるようになります。

次に、新規登録処理のaddメソッドについてです。まず、メソッド定義を変更します。
◾️変更前

public String add(@RequestParam String newdiary)
◾️変更後
public String add(Model model, @Valid NewDiaryForm newDiaryForm, BindingResult bindingResult)

引数にフォームクラスのNewDiaryFormを指定する事で、新規登録フォームの日記本文newdiaryの値はNewDiaryFormのフィールドnewdiaryに入るため、今まで引数に指定していた"@RequestParam String newdiary"は必要なくなるので削除しています。

また、引数のNewDiaryFormには@Validを付けています。@Validを付ける事で、フォームクラスNewDiaryFormの各フィールドのバリデーションチェック機能が有効になります。

そして、NewDiaryFormのバリデーションチェックの結果が、もう一つの引数の"BindingResult bindingResult"に入ります。
具体的に言うと、bindingResultにはエラーが存在したか、エラーが何個あるか、エラー内容などの情報が入ります。一度デバッグをしてbindingResultの値を確認すると、イメージが掴みやすくなると思います。

◾️@Valid、@Validated

上では引数のフォームクラスに@Validを付けていますが、@Validはjavax.validation.Validで、JavaのJakarta Bean Validationです。
また、@Validとは別に@Validatedもあります。@Validatedはorg.springframework.validation.annotation.Validatedで、Springの機能です。

今回のコードでは、@Validを@Validatedに変更しても問題なく動作します。というより、Sprin Bootのアプリなのだから、@Validatedを使用した方が良いのかもしれませんね。

次に、addメソッド内の最初のコードです。

if (bindingResult.hasErrors()) {
  return summary(model, newDiaryForm);
}
bindingResultのhasErrorsメソッドでバリデーションエラーが存在したかどうかを判別し、エラーがあった場合は登録処理をせずにsummaryメソッドを呼び出し、日記一覧情報をエラーメッセージと一緒にフロントエンドへ返すようにしています。

この時、addメソッドやsummaryメソッド内では、modelにnewDiaryFormとbindingResultの値がセットされています。これはSpringが裏で自動でやってくれるためです。そして、modelの値はフロント側へ返されますので、フロント側ではエラーがあればエラーメッセージを表示するようになります。

あとは、addメソッド内の修正箇所は1行だけです。

Diary diary = new Diary(newDiaryForm.getNewdiary(), LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS));
引数の"@RequestParam String newdiary"を削除したので、投稿された日記本文のデータは、フォームクラスのnewDiaryFormから取得するようにしています。

フロントエンドの変更、エラーメッセージの表示

続いて、フロント側の日記一覧情報と新規登録フォームがあるsummary.htmlです。エラーメッセージを表示するsummary.htmlを示します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>【Spring Boot】日記投稿ウェブアプリ【Thymeleaf】</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body class="container mt-3">
<h1 class="display-5">日記一覧</h1>
<table class="table table-striped table-hover table-sm">
<tr><th></th><th>投稿日時</th><th></th><th></th></tr>
<tr th:each="diary : ${diarys}">
<td th:text="${diary.bodytext}"></td>
<td th:text="${diary.createDatetime}"></td>
<td><a th:href="@{/diary/edit(id=${diary.id})}" class="btn btn-primary">編集</a></td>
<td>
<form th:action="@{/diary/delete}" method="post">
<input type="hidden" name="id" th:value="${diary.id}"/>
<input type="submit" class="btn btn-danger" value="削除"/>
</form>
</td>
</tr>
</table>
<h3>新規投稿</h3>
<form  th:action="@{/diary/add}" method="post">
<input type="text" name="newdiary" class="form-control mb-2">
<button type="submit" class="btn btn-primary">投稿する</button>
<div th:if="${#fields.hasErrors('newDiaryForm.newdiary')}" th:errors="*{newDiaryForm.newdiary}" class="alert alert-danger" >投稿エラー</div>
</form>
</body>
</html>

summary.htmlのソース全体を書きましたが、第7回のページのsummary.htmlからの変更箇所は、1行コードを追加しただけです。

<div th:if="${#fields.hasErrors('newDiaryForm.newdiary')}" th:errors="*{newDiaryForm.newdiary}" class="alert alert-danger" >投稿エラー</div>
新規投稿フォームの日記本文の内容がバリデーションチェックに引っかかった場合、エラーメッセージを表示しています。
th:if="${#fields.hasErrors('newDiaryForm.newdiary')}"で、フォームクラスnewDiaryFormのフィールドnewdiaryにエラーがあったかどうかを判別し、エラーがあればth:errors="*{newDiaryForm.newdiary}"でエラーメッセージを表示しています。

アプリの実行とブラウザで動作確認

それではSpring Bootアプリを起動して、ブラウザで動作確認をします。

まずは日記一覧ページにアクセスします。アクセスするURLは「http://localhost:8080/diary/summary」です。
そして、新規投稿フォームに何も入力しないで(もしくは1文字か2文字だけ入力して)投稿するボタンを押すと、登録処理はされずにエラーメッセージが表示されるはずです。
Spring Bootの入力フォームバリデーション

最後に、次回

以上今回は、日記の新規投稿フォームにバリデーション機能を追加しました。

今回は投稿フォームがテキスト1つだけのシンプルなSpring Bootアプリなので、空文字チェックと文字数制限チェックしかやりませんでしたが、JavaのBean Validation(javax.validation.constraintsパッケージ)では、他にも日付、メールアドレス、真偽、パターンチェックなどができますし、Hibernateのバリデーション(org.hibernate.validator.constraintsパッケージ)では、URL、クレジッドカード番号のチェックなどもできます。

また、独自のバリデーションアノテーションを作る事もできますので、機会があればレガシーなJavaコードでのチェックではなく、アノテーションの自作を試してみるのもいいかもしれません。

次回ですが、日記の編集画面の作成と更新処理機能を追加していきます。CRUDでいうと、U(Update、データ更新)の部分にあたります。

(第11回) 日記の編集画面と更新機能を追加、CRUDのU(Update、更新)【Spring Boot2で日記ウェブアプリ】
Spring Bootの日記投稿ウェブアプリ開発入門トップ