Spring Bean Validationで相関チェックを行う

2024年1月13日

@AssertTrueを付加したチェックメソッドを作成する

Spring MVCにおいて@RestControllerを付与して作成したAPIへPOSTするリクエスト内で相関チェックを行いたい場合、リクエストのBean内に@AssertTrueを付与したチェックメソッドを作成することで簡単に実現できる。

例として以下のクラスを考える。toが入力された場合のみmessageを必須入力としている。相関チェックに限らず@AssertTrueを付与したメソッドを作成することで自作のバリデーションを簡単に実装することができる。

@AssertTrueを付与するメソッド名は"is"または"get"で始まらなければいけないことに注意

import lombok.Data;
import org.springframework.util.StringUtils;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;

@Data
public class Greeting2 {

    private String to;
    private String message;

    @Min(value = 10L)
    private int count;

  // 相関チェックを行うメソッド
    @AssertTrue(message = "message must not be empty if to is not empty.")
    public boolean isMessageNotEmptyIfToPresents() {

        if (StringUtils.isEmpty(to)) {
            return true;
        }

        return !StringUtils.isEmpty(message);
    }
}

コントローラのリクエストを処理するメソッドのリクエストボディに当たる引数には@Validated付与する。バリデーションエラー時にはMethodArgumentNotValidExceptionが投げられるため、@RestControllerAdviceを付与して作成したエラーハンドラで処理する。

バリデーションエラーを@RestControllerAdviceで処理する

Spring MVCでAPIを作成した場合、@RestControllerAdviceを付与して共通のエラーハンドラを作成することができる。リクエストに対するバリデーションエラーが発生した場合、MethodArgumentNotValidExceptionが投げられるる。バリデーションエラーを処理したい場合、エラーハンドラ内にこの例外を処理するメソッドを定義すればよい。

バリデーションエラーはMethodArgumentNotValidExceptionクラスのBindingResultに格納されている。例として、複数のバリデーションエラーが発生した場合に全てのエラーメッセージをエラーレスポンスとして返す処理を作成する。

import java.util.stream.Collectors;

@RestControllerAdvice
public class ErrorController {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleException(MethodArgumentNotValidException e) {

        // BindingResult中の全エラーメッセージを「,」で連結しレスポンスへ入れる
        var response = new ErrorResponse("error", e.getBindingResult().getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(",")));
        return response;
    }

    @Data
    @AllArgsConstructor
    private static class ErrorResponse {

        private String id;
        private String message;
    }
}

バリデーションの単体テスト

バリデーションルールが複雑な場合はバリデーションのみを単体テストした方がテストコードをシンプルに保つことができる。jUnitでのBean Validationのテスト方法は【Spring Boot】 Bean ValidationをjUnitで単体テストするにまとめている。

参考にしたサイト