본문 바로가기
Spring

@Valid exception handling (with BindException)

by soro.k 2022. 11. 29.

 

DTO 객체의 유효성을 검증하려고 @Valid 어노테이션을 사용하기로 했다. 이미 프로젝트 설정에 spring-boot-starter-validation 이 추가되어있다면 따로 설정해야 할 건 없다.

참고로 Spring Boot 2.3부터는 반드시 의존성을 추가해야 한다.

 

 

PostDTO.SaveRequest

public class PostDTO {

    @Getter
    @Builder
    public static class SaveRequest {

        private Long userId;

        @NotNull(message = "카테고리를 선택해 주세요.")
        private Long categoryId;

        @NotBlank(message = "제목을 입력해 주세요.")
        private String title;

        @NotBlank(message = "내용을 입력해 주세요.")
        private String content;

        private List<MultipartFile> multipartFiles;

    }
}

 

 String 타입에는 Null 값과 "", " " 모두 검증하기 위해  @NotBlank를, Long 타입에는 @NotNull을 사용했다.

 

 

참고. 검증 어노테이션의 허용 범위

  Null "" " "
@NotNull X O O
@NotEmpty X X O
@NotBlank X X X

 

 

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/posts")
public class PostController {

    private final PostService postService;

    @PostMapping("/create")
    public ResponseEntity<Long> savePost(@Valid PostDTO.SaveRequest dto) {
        return ResponseEntity.ok(postService.savePost(dto));
    }
}

Controller 단에서 검증하려는 객체 앞에 @Valid 어노테이션을 붙여준다.

 

이렇게 하고 나서 검증을 통과하지 못하는 값을 요청 보내면 아직까지는 불친절한 400 Bad Request 에러 메시지를 보게 된다.

 

 

 

정확히 어떤 컬럼이 어떤 검증을 통과하지 못했는지 메시지를 받기 위해서는 ExceptionHandler 설정을 해줘야 한다.

나는 ExceptionHandler 코드를 추가하면서 계속해서 위와 똑같은 응답 메시지를 받게 됐는데 어떻게 해결했는지 기록하고자 한다.

 

 

TestExceptionHandler

@RestControllerAdvice
public class TestExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
        Map<String, String> errors = new HashMap<>();
        exception.getBindingResult().getAllErrors()
                .forEach(e -> errors.put(((FieldError) e).getField(), e.getDefaultMessage()));
        log.debug("erros = {}", errors);
        return ResponseEntity.badRequest().body(errors);
    }

}

이렇게 하면 @Valid에 의해 검증되지 못한 필드와 에러 메시지를 Map에 넣어 응답 메시지로 보내준다. 참고한 코드의 출처는 쟈미님의 블로그이다.

 

 

사실 참고했던 코드는 위와 같이 MethodArgumentNotValidException에 대한 Handler가 적용되는 에러였는데 내가 만난 에러 로그에서 Exception이 발생한 곳은 MethodArgumentNotValidException 클래스가 상속하고 있는 BindException이었다.

 

 

그래서 Exception을 MethodArgumentNotValidException이 아닌 BindException으로 변경해줬다.

@ExceptionHandler(BindException.class)
public ResponseEntity<Map<String, String>> handleBindException(BindException exception) {
    Map<String, String> errors = new HashMap<>();
    exception.getBindingResult().getAllErrors()
            .forEach(e -> errors.put(((FieldError) e).getField(), e.getDefaultMessage()));
    log.debug("erros = {}", errors);
    return ResponseEntity.badRequest().body(errors);
}

 

 

이렇게 하면 똑같은 400 Bad Request이지만 검증을 통과하지 못한 컬럼 명과 경고 메시지를 확인할 수 있다.