3
5

More than 1 year has passed since last update.

Java: Spring APIのバリデーション実装について

Last updated at Posted at 2022-10-15

はじめに

  • とあるプロジェクトで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のプロパティが取り得る値や条件を、制約のアノテーションとして設定することで、バリデーション内容を定義する仕組み」です。バリデーション仕様を実装したライブラリを使って、バリデーションを実装していきます。

参考

Bean Validation 公式ページ

Bean Validationの基本

Spring Frameworkで使用できるBean Validationアノテーション一覧は下記のサイトを参照
Spring Frameworkで使用できるBean Validationアノテーション一覧

実装方法

  • バリデーションチェック実装の流れを順を追って確認

1. 入力チェックの有効化/DTO作成

  • Spring MVCのデフォルト動作では、リクエストパラメータに対する入力チェックは実行されません。
  • 入力チェックを行う場合には、入力チェックを行うメソッドの引数にリクエストパラメータをマッピングするDTOクラスを定義し、@org.springframework.validation.annotation.Validated(@Validated を指定します。(指定する場所はControllerのメソッド内)

サンプルDTO: UserRequest.java

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);
    }

単体テスト

  • 各フィールドに適用したバリデーションルールが効いているか、単体テストコードで確認する。以下、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作れます!!
3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5