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

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

動作環境と今回の目的

最終更新日:2021/5/19

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

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

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

◾️動作環境やバージョンは以下の通りです。
OS:macOS Catalina(バージョン10.15.5)
開発環境:Eclipse(Pleiades All in One、4.16(2020-06)、Java Full Edition版)
Spring Bootバージョン:2.3.3、2.4.0
Java:11
データベース:H2
Bootstrap4.5.3

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

今回作成するファイル

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

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

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

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

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

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

もしライブラリの依存関係で「検証」にチェックを入れずにプロジェクトを作成した場合、pom.xmlファイルのdependenciesタグ直下にdependencyタグを追加すれば使えるようになるはずです。

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

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

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

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

今回は新規投稿フォームで登録するものは日記本文だけですので、チェックするのも日記本文だけです。
そして、日記本文のチェック項目は、空文字禁止、文字数制限が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;

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

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

続いて、コントローラクラス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オブジェクトをフロント側へ返すという流れになりますので、フロント側で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 rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" 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の日記投稿ウェブアプリ開発入門トップ

[広告] Kindle Unlimitedなら対象の本が定額で読み放題
対象本には、プログラミングなどのIT関連本、マンガ、雑誌、ビジネス書などがたくさんあります!