はじめに
- とあるプロジェクトでJava × SpringでAPIを製造したのですが、その際にバリデーションについて調査したので、備忘録として内容をまとめてみました
- Java × Spring でAPIを作成した際にリクエスト段階でバリデーションチェックをしたいときに有効
環境
- Java 11
- Spring Boot 2系
- Lombok
前提
- Java × Spring でWeb APIを実装
- APIリクエスト時に、RequestDTOを用いて送信されるJSONリクエストをRequestDTOにマッピングする想定
Spring / APIのバリデーション実装
- Springは、Java標準であるJSR-303のBean Validationをサポートしています。Spring MVCでの開発においては、Bean Validationを利用してバリデーションをする方法が推奨されています。
※ JSR-303:Bean Validationは「JavaBeansのプロパティが取り得る値や条件を、制約のアノテーションとして設定することで、バリデーション内容を定義する仕組み」です。バリデーション仕様を実装したライブラリを使って、バリデーションを実装していきます。
参考
Spring Frameworkで使用できるBean Validationアノテーション一覧は下記のサイトを参照
Spring Frameworkで使用できるBean Validationアノテーション一覧
実装方法
- バリデーションチェック実装の流れを順を追って確認
1. 入力チェックの有効化/DTO作成
- Spring MVCのデフォルト動作では、リクエストパラメータに対する入力チェックは実行されません。
- 入力チェックを行う場合には、入力チェックを行うメソッドの引数にリクエストパラメータをマッピングするDTOクラスを定義し、@org.springframework.validation.annotation.Validated(@Validated) を指定します。(指定する場所はControllerのメソッド内)
サンプルDTO: UserRequest.java
- DTOクラス。各フィールドに適用したいルール(@アノテーション)を指定する。
- 具体的なアノテーションについては Spring Frameworkで使用できるBean Validationアノテーション一覧を参照
package jp.XXXX.controller;
import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* サンプル:ユーザー情報取得時DTOクラス
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRequest {
@NotNull(message = "ユーザーIDは必須です。")
@DecimalMax(value = "999999999", message = "ユーザーIDは{value}以下を指定してください。")
private Integer userId;
}
サンプル: UserController.java #getUserInfoById()
- SpringのControllerでは、 @RequestBodyを使用することでHTTPリクエストボディに設定されたパラメータをJSONで受け取り、受取ったJSONを指定したDTOクラスにマッピングします。
- サンプルでは、POSTメソッドを想定
- @org.springframework.validation.annotation.Validated(@Validated) はバリデーションの有効化を定義
- @org.springframework.validation.BindingResult(BindingResult) には、リクエストデータのバインディングエラーと入力チェックエラー情報が格納されます。
package jp.XXXX.controller;
中略
/**
* ユーザーID指定で、ユーザー情報を取得
*
* @return ユーザー情報取得
*/
@PostMapping("/userInfo")
public ResponseEntity<User> getUserInfoById(
@RequestBody @Validated UserRequest request, BindingResult result)
throws BindException {
2. 入力チェック結果の判定
- 入力チェックの実行結果を取得し、エラーがあった場合はエラー結果(
BindingResult
)をBindExceptionにスローします。
@PostMapping("/userInfo")
public ResponseEntity<User> getUserInfoById(
@RequestBody @Validated UserRequest request, BindingResult result)
throws BindException {
if (result.hasErrors()) {
throw new BindException(result);
}
- BindException の詳細は BindException を参照
単体テスト
- 各フィールドに適用したバリデーションルールが効いているか、単体テストコードで確認する。以下、DTOクラスを対象したテストコード例。
サンプル: UserRequestTest.java
package jp.XXXX.controller;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class UserRequestTest {
// Validatorの実行クラス
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
@Test
public void testOk() {
UserRequest requestDto =
new UserRequest(Integer.valueOf(1));
Set<ConstraintViolation<UserRequest>> violations =
validator.validate(requestDto);
assertThat(violations.size()).isEqualTo(0);
}
@Test
public void testNotNull_1() {
UserRequest requestDto = new UserRequest();
Set<ConstraintViolation<UserRequest>> violations =
validator.validate(requestDto);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations).extracting("message").containsOnly("ユーザーIDは必須です。");
}
@Test
public void testNotNull_2() {
UserRequest requestDto =
new UserRequest(null);
Set<ConstraintViolation<UserRequest>> violations =
validator.validate(requestDto);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations).extracting("message").containsOnly("ユーザーIDは必須です。");
}
@Test
public void testDecimalMax() {
UserRequest requestDto =
new UserRequest(1234567890);
Set<ConstraintViolation<UserRequest>> violations =
validator.validate(requestDto);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations).extracting("message").containsOnly("ユーザーIDは999999999以下を指定してください。");
}
}
最後に
- Java × Spring でサクッとAPI作れます!!