1. はじめに
今回はBean Validation
を利用した入力チェックの方法について説明したいと思います。
- Bean Validationの使い方
- 入力チェックの対象クラスにチェックに応じたアノテーションを付与する
- チェック対象となるインスタンスを生成する
-
Validator
のvalidate
メソッドにチェック対象のインスタンスを指定し、入力チェックを実行する
2. ライブラリの用意
必要となるライブラリを依存関係に追加します。
hibernate-validator
のメッセージ解決で必要となるためjavax.el
も一緒に追加しています。
なお、Bean Validationのライブラリ(groupId:javax.validation、artifactId:validation-api
)を記述していませんが、依存関係があるため自動で依存関係に追加されます。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.9.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b09</version>
</dependency>
3. モデルの定義
チェック対象となるモデルクラスを定義します。この際、各チェックに応じたアノテーションを付与します。付与可能なアノテーションは以下の3種類です。
- Bean Validation(javax.validation.*)
- Hibernate Validator(org.hibernate.validator.constraints.*)
- 独自のカスタムValidator
数値の最大値を制限する@Max
のように、チェックによっては制限値が必要になりますが、これはアノテーションの属性として設定します。設定可能な属性はアノテーションごとに異なります。
チェックでエラーになった際に表示するメッセージを変更したい場合、message
属性でオーバーライドします。message
属性で設定したメッセージが最優先されます。
package com.example.beanvalidation.app;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull
// @Pattern(regexp = "[0-9,-]+")
@Pattern(regexp = "[0-9,-]+", message = "入力された値 ${validatedValue} は正しくありません。ISBMの形式で入力してください。")
private String isbn;
@NotNull
@Size(max = 200)
private String title;
@Max(9999)
@Min(1)
private int pageCount;
@NotNull
@Past
private Date publishedDate;
// constructor, setter, getter omitted
}
4. エラーメッセージの変更
Bean Validation
は仕様のためのエラーメッセージは実装ライブラリに依存します。今回であればhibernate-validator
が定義したメッセージがデフォルトになります。
このデフォルトメッセージを変更したい場合、クラスパス直下に配置したValidationMessages.properties
でオーバライドすることが可能です。
- プロパティのキー : アノテーションのFQCN.message
- プロパティの値 : デフォルトメッセージの内容
今回は@Past
のデフォルトメッセージを変更してみたいと思います。
# javax.validation.constraints.Past.message=入力された日付 ${validatedValue} は正しくありません。過去日を入力してください。
javax.validation.constraints.Past.message=\u5165\u529B\u3055\u308C\u305F\u65E5\u4ED8 ${validatedValue} \u306F\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002\u904E\u53BB\u65E5\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002
(注意)
- STS(Spring Tool Suite)のプロパティエディタでは自動で変換してくれるため日本語がunicode変換されています。
- メッセージ内で入力された値は
${validatedValue}
で参照可能です。 -
ValidationMessages.properties
は多言語対応可能で日本語の場合はValidationMessages_ja.properties
になります。
5. 基本的な使い方
前述で定義したBook
クラスにいろいろ値を設定して、実際に入力チェックを実施してみたいと思います。
DIコンテナを利用するとValidator
をインジェクションするのが一般的ですが、今回はmain
メソッドのある普通のJavaアプリとします。
といってもValidator
の使い方は同じです。
package com.example.beanvalidation.app;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class ValidationDemo {
public static void main(String[] args) {
// 1. create validator
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
// 2. create target object
Book book = createBook();
// 3. validate
Set<ConstraintViolation<Book>> constraintViolations = validator
.validate(book);
// 4. check result
int errorCount = constraintViolations.size();
System.out.println("validate error count : " + errorCount);
if (errorCount > 0) {
showErrorDetails(constraintViolations);
}
System.out.println("=== demo : validate error ===");
// 2. create target object
book = createNgBook();
// 3. validate
constraintViolations = validator.validate(book);
// 4. check result
errorCount = constraintViolations.size();
System.out.println("validate error count : " + errorCount);
if (errorCount > 0) {
showErrorDetails(constraintViolations);
}
}
private static <T> void showErrorDetails(
Set<ConstraintViolation<T>> constraintViolations) {
for (ConstraintViolation<T> violation : constraintViolations) {
System.out.println("----------");
System.out.println(
"MessageTemplate : " + violation.getMessageTemplate());
System.out.println("Message : " + violation.getMessage());
System.out.println("InvalidValue : " + violation.getInvalidValue());
System.out.println("PropertyPath : " + violation.getPropertyPath());
System.out.println("RootBeanClass : " + violation.getRootBeanClass());
System.out.println("RootBean : " + violation.getRootBean());
}
}
private static Book createBook() {
Date publishedDate = Date.from(LocalDateTime.of(2017, 7, 21, 0, 0, 0)
.toInstant(ZoneOffset.ofHours(9)));
return new Book("978-4798142470",
"Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発", 744,
publishedDate);
}
private static Book createNgBook() {
Date publishedDate = Date.from(LocalDateTime.of(2999, 7, 21, 0, 0, 0)
.toInstant(ZoneOffset.ofHours(9)));
return new Book("ERROR-ISBN-9999", null, 0, publishedDate);
}
}
6. 実行結果(おまけ)
参考までに、前述のプログラムを実行した際の結果を以下に示します。
validate error count : 0
=== demo : validate error ===
validate error count : 4
----------
MessageTemplate : {javax.validation.constraints.Min.message}
Message : must be greater than or equal to 1
InvalidValue : 0
PropertyPath : pageCount
RootBeanClass : class com.example.beanvalidation.app.Book
RootBean : Book [isbn=ERROR-ISBN-9999, title=null, pageCount=0, publishedDate=Sun Jul 21 00:00:00 JST 2999]
----------
MessageTemplate : {javax.validation.constraints.Past.message}
Message : 入力された日付 Sun Jul 21 00:00:00 JST 2999 は正しくありません。過去日を入力してください。
InvalidValue : Sun Jul 21 00:00:00 JST 2999
PropertyPath : publishedDate
RootBeanClass : class com.example.beanvalidation.app.Book
RootBean : Book [isbn=ERROR-ISBN-9999, title=null, pageCount=0, publishedDate=Sun Jul 21 00:00:00 JST 2999]
----------
MessageTemplate : {javax.validation.constraints.NotNull.message}
Message : must not be null
InvalidValue : null
PropertyPath : title
RootBeanClass : class com.example.beanvalidation.app.Book
RootBean : Book [isbn=ERROR-ISBN-9999, title=null, pageCount=0, publishedDate=Sun Jul 21 00:00:00 JST 2999]
----------
MessageTemplate : 入力された値 ${validatedValue} は正しくありません。ISBMの形式で入力してください。
Message : 入力された値 ERROR-ISBN-9999 は正しくありません。ISBMの形式で入力してください。
InvalidValue : ERROR-ISBN-9999
PropertyPath : isbn
RootBeanClass : class com.example.beanvalidation.app.Book
RootBean : Book [isbn=ERROR-ISBN-9999, title=null, pageCount=0, publishedDate=Sun Jul 21 00:00:00 JST 2999]
7. さいごに
今回はBean Validation
による入力チェックの方法について説明しました。
チェック用のアノテーションを付与するだけで、入力チェックの仕様を定義できるのでシンプルで分かりやすいかと思います。