背景・目的
前回、下記でデータの登録・更新・削除などを試しました。今回はバリデーションを試してみます
まとめ
下記に特徴をまとめます
特徴 | 説明 |
---|---|
バリデーションのパッケージ | spring-boot-starter-validation |
パッケージは二種類ある | ・jakarta.validation ・jakarta.validation.constraints |
標準バリデーション | 以降のものがある |
入力されているかのバリデーション |
@Null @NotNull @NotBlank @NotEmpty
|
数値の範囲のバリデーション |
@Min @Max @DecimalMin @DecimalMax
|
数値の桁数のバリデーション | @Digits |
日時のバリデーション |
@Future @Past
|
オブジェクト数 | @Size |
正規表現 | @Pattern |
バリデーションのカスタマイズ | エラーメッセージをカスタマイズするには、下記の方法がある ・アノテーション内のmessageのバリューに記載する ・resource/ValidationMessages.properties内に記載する バリデーション自体を実装する |
概要
Spring Boot 3 プログラミング入門を基に整理します
- SpringBootのバリデーションには、spring-boot-starter-validation というパッケージが用意されている
validationのアノテーション
Springではバリデーションは、2種類用意されている。
- jakarta.validation
- jakarta.validation.constraints
アノテーション | 説明 | 備考 |
---|---|---|
@Null |
Nullである | |
@NotNull |
Nullではない | 何も入力しない場合、空文字になるのですり抜けるので注意が必要 |
@NotBlank |
空白ではない | 入力フォームはこちらがよい |
@NotEmpty |
空ではない | NotBlankとの違いは、半角スペース等でも入力したとみなす |
@Min |
最小値 | |
@Max |
最大値 | |
@DecimalMin |
BigDecimal、int、long等値全般で使える最小値 | |
@DecimalMax |
BigDecimal、int、long等値全般で使える最大値 | |
@Digits |
整数と小数部分の桁数制限を行う | 使い方。@Digits(integer=5,fraction=10)
|
@Future |
現在より先の日時のみ受付可能 | |
@Past |
現在よりも過去の日時のみ受付可能 | |
@Size |
文字列、配列、コレクションなど、いくつもの値をまとめて保管するオブジェクトで使用する | @Size(min=1,max=10) |
@Pattern |
String項目で使う。正規表現のパターンを指定して入力チェックを行う |
エラーメッセージ
- バリデーションで表示されるエラーメッセージは、あらかじめバリデーションパッケージに用意されている
- エラーメッセージをカスタマイズする場合は、messageを使用して表示すれば良い
エラーメッセージをプロパティファイルで定義
- エンティティクラスのアノテーション部分にメッセージを直接記述するのは、メンテナンス性が良くない
- メッセージをプロパティにまとめて扱うのが良い
- resourcesフォルダに、ValidationMessages.propertiesに定義する
オリジナルのバリデーター
- アノテーションで実装されたバリデーションは、バリデータと呼ばれるデータチェック用のクラスにより行われる
- 不足する場合は、自作も可能
- 自作には2つのクラスが必要
- アノテーションクラス
- バリデーションを行うバリデータクラス
- アノテーションクラス
@interface アノテーション名とする
- java.lang.annotation.Annotationインタフェイスのサブクラスとして認識される
- バリデータクラス
- jakarta.validation.ConstraintValidatorインタフェイスを実装する
実践
Spring Boot 3 プログラミング入門を参考に試してみます。
前提
Spring Bootでデータを保存する-その2(更新と削除)で実装した内容を基にしています
pom.xml
- 下記のdependencyを追加します
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>3.3.1</version> </dependency>
バリデーションの追加
- Personエンティティを開きます
- 下記の追加を行います
- NotBlank
@Column(length = 50,nullable = false) @NotBlank private String firstName; @Column(length = 50,nullable = false) @NotBlank private String lastName;
ハンドラの修正
- WebControllerのformメソッドを下記のように修正します
@RequestMapping(value = "/", method=RequestMethod.POST) @Transactional public ModelAndView form( @ModelAttribute("formModel") @Validated Person person, BindingResult result, ModelAndView mav) { ModelAndView res = null; System.out.println(result.getFieldErrors()); if(!result.hasErrors()) { repository.saveAndFlush(person); res = new ModelAndView("redirect:/"); } else { mav.setViewName("index"); mav.addObject("title","Test page"); mav.addObject("message","Sorry, error is occured..."); List<Person> list = repository.findAll(); mav.addObject("people", list); res = mav; } return res; }
-
@Validated
- バリデーションチェックのもの
- エンティティの値をバリデーションチェックするためのもの
- エンティティの各値を自動的にチェックする
- バリデーションチェックのエラー
- BindingResultで結果がわかる
- hasErrorsメソッドでエラー有無がわかる
-
テンプレートの修正
- index.htmlを修正します
<form method="post" action="/" th:object="${formModel}">
<ul>
<li th:each="error : ${#fields.detailedErrors()}" class="text text-danger" th:text="${error.message}" />
</ul>
動作確認
-
ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
-
test-taroとtest-suzukiを入力し、「Create」を入れると登録されました
エラーメッセージの位置を変更する
各フィールド配下に、エラーメッセージを表示したほうがわかりやすいので改善します
- index.htmlを下記のように修正します
<form method="post" action="/" th:object="${formModel}"> <style>.err {color:red}</style> <div class="mb-3"> <label for="name" class="form-label">FirstName</label> <input type="text" class="form-control" name="firstName" th:value="*{firstName}" th:errorclass="err" /> <div th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" th:errorclass="err"></div> </div> <div class="mb-3"> <label for="name" class="form-label"></label>LastName</label> <input type="text" class="form-control" name="lastName" th:value="*{lastName}" th:errorclass="err" /> <div th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" th:errorclass="err"></div> </div> <div class="mb-3"> <input type="submit" class="btn btn-primary" value="Create" /> </div> </form>
動作確認
-
ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
エラーメッセージのカスタマイズ
バリデーションの引数を入れることで、メッセージをカスタマイズできます。
- バリデーションのアノテーションにメッセージを指定します
@Column(length = 50,nullable = false) @NotBlank(message="名前(FirstName)を入力してください!") private String firstName; @Column(length = 50,nullable = false) @NotBlank(message="名字(LastName)を入力してください!") private String lastName;
動作確認
- ビルドしSpring Bootを再実行します
$ mvn package $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
- localhost:8080にアクセスします
- 何も入力せずに、「Create」をクリックすると、カスタマイズ後のエラーメッセージが表示されました
エラーメッセージのカスタマイズ(プロパティファイル)
エラーメッセージをハードコードするのではなく、プロパティファイルに書いておきます。
-
先ほどのmessageを削除しておきます(エンティティ内のmessageが優先されるため)
@Column(length = 50,nullable = false) @NotBlank private String firstName; @Column(length = 50,nullable = false) @NotBlank private String lastName;
-
下記の設定を追記します
jakarta.validation.constraints.NotBlank.message=must not be blank
動作確認
-
ビルドしSpring Bootを再実行します
$ mvn package $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
-
localhost:8080にアクセスします
バリデータの実装
Validator
PhoneValidatorを実装する
-
下記のコードを追記します
package com.example.data.rest_db; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; public class PhoneValidator implements ConstraintValidator<Phone, String> { @Override public void initialize(Phone phone) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { return value != null && value.matches("[0-9()-]*"); } }
- ConstraintValidatorインタフェイスの実装
- パラメータは、Phoneと、String
- Phoneがアノテーション
- Stringが入力値
- Stringを入力し、Phoneアノテーションによりバリデーションが設定される
- initialize
- 初期化メソッド
- アノテーションの情報を取得できる
- isValid
- バリデーション
- Stringで入力値、ConstraintValidatorContextが渡される
- パラメータは、Phoneと、String
- ConstraintValidatorインタフェイスの実装
アノテーション
-
下記のコードを追記します
package com.example.data.rest_db; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import jakarta.validation.Constraint; import jakarta.validation.Payload; @Documented @Constraint(validatedBy = PhoneValidator.class) @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Phone { String message() default "Invalid phone number"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
バリデータを使用する
テンプレートの修正
Phoneバリデータを使用します
- index.htmlを修正します。Phoneを追加します
<body class="container"> <h1 class="display-4 mb-4" th:text="${title}"></h1> <p th:text="${message}"></p> <form method="post" action="/" th:object="${formModel}"> <style>.err {color:red}</style> <div class="mb-3"> <label for="name" class="form-label">FirstName</label> <input type="text" class="form-control" name="firstName" th:value="*{firstName}" th:errorclass="err" /> <div th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" th:errorclass="err"></div> </div> <div class="mb-3"> <label for="name" class="form-label"></label>LastName</label> <input type="text" class="form-control" name="lastName" th:value="*{lastName}" th:errorclass="err" /> <div th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" th:errorclass="err"></div> </div> <div class="mb-3"> <label for="name" class="form-label"></label>Phone</label> <input type="text" class="form-control" name="phone" th:value="*{phone}" th:errorclass="err" /> <div th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}" th:errorclass="err"></div> </div> <div class="mb-3"> <input type="submit" class="btn btn-primary" value="Create" /> </div> </form> <table class="table"> <thread> <tr> <th>firstName</th> <th>lastName</th> <th>Phone</th> </tr> </thread> <tbody> <tr th:each="person : ${people}"> <td th:text="${person.firstName}"></td> <td th:text="${person.lastName}"></td> <td th:text="${person.phone}"></td> </tr> </tbody> </table> </body>
エンティティの修正
- phone変数と、アクセスメソッド(SetterとGetter)を追加します。また、 作成した
@Phone
アノテーションを追加します@Column(nullable = false) @Phone private String phone; // Add this field public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; }
初期値の設定
- ハンドラのinitを修正します。phoneの初期値を埋めます
public void init() { Person p1 = new Person(); p1.setFirstName("Ichiro"); p1.setLastName("Suzuki"); p1.setPhone("00-0000-0000"); repository.saveAndFlush(p1); Person p2 = new Person(); p2.setFirstName("Taro"); p2.setLastName("Yamada"); p2.setPhone("00-0000-0001"); repository.saveAndFlush(p2); Person p3 = new Person(); p3.setFirstName("Hanako"); p3.setLastName("Yamada"); p3.setPhone("00-0000-0003"); repository.saveAndFlush(p3); }
動作確認
-
ビルドしSpring Bootを再実行します
$ mvn package $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
考察
今回は、Spring Bootにおけるバリデーションについて、標準で用意されているものの利用から、エラーメッセージのカスタマイズ、バリデーションの自作まで試してみました。
引き続き、SpringBootの機能について学んでいきます。
参考