본문 바로가기
BE/🍃 Spring

[Spring Boot] Validation시 발생할 수 있는 에러 알아보기

by 틴디 2024. 11. 8.
반응형

이전글 : 2024.11.07 - [BE/🍃 Spring] - [Validation] spring boot validation 사용법과 종류

 

:: Spring Boot :: v3.3.5

 

 

 @Valid와 @Validated

  • Valid와 Validated 검증 실패시 exception 발생
  •  @Valid 
    • @Valid는 jakarta.validation.Valid에 속해 있고 RequestBody로 전달된 요청 객체를 검증하는데 사용됨
  •  @Validated 
    • @Validated는 org.springframework.validation.annotation.Validated에 속해 있고, AOP 기반으로 동작
    • @RequestParam, @PathVariable에서 전달되는 데이터의 유효성을 검증
    • Controller가 아닌 곳 (ex Service) 에서 데이터의 검증이 필요한 경우 사용

 

예제 코드

 

BookApiController.java

@RestController
@RequestMapping("/api/v1/book")
@Validated
public class BookApiController {
    @PostMapping
    public String regist(
            @RequestBody @Valid RegistBookRequest request
    ) {
        return request.toString();
    }

    @GetMapping("/{book_name}")
    public String detail(
        @PathVariable(name = "book_name") @Size(min = 3, max = 20, message = "책의 이름은 3~20 자리로 등록해 주세요.")
        String bookName
    ) {
        return bookName;
    }
}

 

RegistBookRequest.java

@Getter
@ToString
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class RegistBookRequest {
    @Size(min = 3, max = 20, message = "책의 이름은 3~20 자리로 등록해 주세요.")
    private String bookName;

    @NotBlank(message = "카테고리 값은 필수 입니다.")
    private String category;

    @Past(message = "발행일은 과거날짜만 가능합니다. ")
    private LocalDate issuedDay;

    @Max(value = 100, message = "최대로 등록할 수 있는 수량은 100개 입니다.")
    private int amount;
}

 

Validation시 발생 가능한 Exception

 

MethodArgumentNotValidException

  • MethodArgumentNotValidException은 Controller에서 @RequestBody를 @Valid로 검증시 발생
  • BindingResult로 필드별 오류를 확인할 수 있음
// http://localhost:8080/api/v1/book
{
    "book_name": "te",
    "category": "철학",
    "issued_day": "2023-12-12",
    "amount": 101
}

 

book_name과 amount로 유효하지 않은 데이터를 보냈을 때

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String cohttp://m.youable.validation.controller.BookApiController.regist(com.youable.validation.dto.request.RegistBookRequest) with 2 errors: 

[Field error in object 'registBookRequest' on field 'bookName': rejected value [te]; codes [Size.registBookRequest.bookName,Size.bookName,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [registBookRequest.bookName,bookName]; arguments []; default message [bookName],20,3]; default message [책의 이름은 3~20 자리로 등록해 주세요.]] 

[Field error in object 'registBookRequest' on field 'amount': rejected value [101]; codes [Max.registBookRequest.amount,Max.amount,Max.int,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [registBookRequest.amount,amount]; arguments []; default message [amount],100]; default message [최대로 등록할 수 있는 수량은 100개 입니다.]] ]

 

  • 검증의 실패한 모든 필드에 대한 데이터가 로그로 출력되는 것을 확인할 수 있음
  • 필드명, 어떤 validation이 사용되었는지, default message로 지정한 문자열 확인 가능
  • 이를 이용해서 공통 규격의 response를 클라이언트에게 전달해 줄 수 있음

HandlerMethodValidationException

  • Controller에 AOP기반의 @Validation이 없는 경우 스프링의 MethodValidator 기능이 사용됨
  • org.springframework.web.method.annotation.HandlerMethodValidationException이 발생
@RestController
@RequestMapping("/api/v1/book")
public class BookApiController {
    @GetMapping("/find")
    public String find(
            @RequestParam(name = "book_name") @Size(min = 3, max = 20, message = "책의 이름은 3~20 자리로 등록해 주세요.")
            String bookName,
            @RequestParam(name = "amount") @Max(value = 10, message = "한번에 구매할 수 있는 수량은 최대 10개")
            int amount
    ) {
        return bookName + " " + amount;
    }
}
org.springframework.web.method.annotation.HandlerMethodValidationException: 400 BAD_REQUEST "Validation failure"

 

 

 

ConstraintViolationException

  • 메서드의 파라미터, 리턴 값에 대한 유효성 검증 실패시 발생
  • aop기반으로 검증 수행
  • Spring 6.1 미만 버전에서 발생
// http://localhost:8080/api/v1/book/te
@RestController
@RequestMapping("/api/v1/book")
@Validated
public class BookApiController {    
    @GetMapping("/{book_name}")
    public String detail(
        @PathVariable(name = "book_name") @Size(min = 3, max = 20, message = "책의 이름은 3~20 자리로 등록해 주세요.")
        String bookName
    ) {
        return bookName;
    }
}

 

2024-11-08T10:20:21.490+09:00 ERROR 65319 --- [validation] [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: jakarta.validation.ConstraintViolationException: detail.bookName: 책의 이름은 3~20 자리로 등록해 주세요.] with root cause

jakarta.validation.ConstraintViolationException: detail.bookName: 책의 이름은 3~20 자리로 등록해 주세요.
  • 검증에 실패한 [메서드명.필드명]을 알 수 있음

 

HttpMessageNotReadableException

  • validation 검증 로직이 아닌, json 문자열을 객체로 파싱하는 과정에서 타입이 맞지 않아 발생하는 exception
  • 검증 로직은 아니지만 request 객체를 전달 받는 과정에서 발생할 수 있는 문제이므로 에러 핸들링 필요
  • 아래 예시 코드 참고
// http://localhost:8080/api/v1/book
{
    "book_name": "te",
    "category": "철학",
    "issued_day": "2023-12-12",
    "amount": "총수량"
}

int 형인 amount를 유효하지 않은 String으로 보냈을 때

 

[validation] [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `int` from String "총수량": not a valid `int` value]
  • int 타입을 String 데이터로 받아 JSON 파싱에 에러가 발생했다는 것을 확인할 수 있음
  • 클라이언트에게 어떤 문제가 발생하는지 확인 시켜주는 공통 규격의 response를 클라이언트에게 전달해줄 수 있음

이 외에도 업무하며 마주한 request 데이터 관련 에러가 몇가지 더 있는데, 이러한 에러를 클라이언트가 알 수 있게 제공하지 않으면 클라이언트는 이 에러가 왜 발생하는지 알 수 없음

  • MethodArgumentTypeMismatchException : 클라이언트 request시 전달된 파라미터의 타입이 정의된 Controller 메서드의 파라미터 타입과 일치하지 않을 때 발생 (long 타입으로 정의했으나 파싱 불가능한 문자열이 전달된 경우 등)
@GetMapping("/school")
public String getSchoolById(@RequestParam("id") Long id) {
}

 

  • MissingServletRequestParameterException : 필수 파라미터가 누락된 경우
@GetMapping("/test")
public String getById(@RequestParam(name = "id", required = true) Long id) {
	// 생략
}
  • MissingServletRequestPartException : multipart/form-data 가 아닌 다른 타입으로 보내거나, 데이터를 누락하는 경우 등에 발생
@PostMapping("/upload")
public String handleFileUpload(@RequestPart("file") MultipartFile file) {
    // 생략
}

 

👉 다음 포스팅에서는 이 에러를 핸들링 하는 방법을 알아보겠습니다.

 

 

728x90
반응형

댓글