본문 바로가기
Spring

Spring REST API @Valid

by 옹알이옹 2024. 3. 6.
목차

1. @Valid 사용법

2. GlobalException을 이용한 유효성 예외 처리

 


 1. @Valid 

 

 1-1. @Valid 란

Spring에서 클라이언트에서 넘어온 값에 대한 검증을 쉽게 하기 위해 해당 어노테이션을 사용하며
해당 처리를 DTO객체에서 처리하기 때문에 코드의 응집도가 올라간다는 장점도 있다.

 

우선 형식 체크 외에 빈 값에 대한 체크는 @NotNull, @NotEmpty, @NotBlank을 사용하며 각각의 용도는 살짝 다른데 차이점은 아래와 같다.

@NotNull: 필드 값이 null인지 검증.
@NotEmpty: 필드 값이 null 뿐 아니라 빈 문자열("") 인지도 검증.
@NotBlank: 필드 값이 null, 빈 문자열("") 뿐 아니라 공백("")인지도 검증.

 

보통 문자열에 체크는 @NotBlank를 사용하며, 객체에 대해서는 @NotNull을 사용하여 체크한다.

 

추가적으로 아래와 같이 DTO 내부에 참조 객체가 존재할 경우 해당 필드에도 @Valid를 붙여주어야 한다.
(컨트롤러에서 DTO에 @Valid를 붙이면 해당 객체만 검증을 해주고 내부 참조 객체에 대해서는 해주지 않는다.)

@NotBlank(message = "타이틀은 필수입니다22.")
private String title;

@NotBlank(message = "내용은 필수입니다.")
private String content;

@Valid
private Member member;

 

@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/post")
public CommonResponseEntity<?> createPost(
        @Valid @RequestBody PostCreateDto param
){
    postService.createPost(param);

    String message = messageSource.getMessage("success.create",new String[]{"post"}, Locale.KOREA);

    return new CommonResponseEntity<>(null,message);
}

 1-2. @Valid 동작 결과 

위에서 title 필드에 @NotBlnk를 걸었으니 title에 빈 값을 전달하여 Post맨으로 요청을 보내면 아래와 같은 결과가 나온다.

@NotBlank(message = "타이틀은 필수입니다.")
private String title;

 

POST맨 결과

{
    "timestamp": "2024-03-06T02:09:39.597+00:00",
    "status": 400,
    "error": "Bad Request",
    "path": "/post"
}

다른 프로젝트에서는 

 "errors": [
        {
            "codes": [
                "NotNull.validTestDTO.innerValidTestDTO",
                "NotNull.innerValidTestDTO",
                "NotNull.com.myshop.order.command.application.InnerValidTestDTO",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "validTestDTO.innerValidTestDTO",
                        "innerValidTestDTO"
                    ],
                    "arguments": null,
                    "defaultMessage": "innerValidTestDTO",
                    "code": "innerValidTestDTO"
                }
            ],
            "defaultMessage": "innerValidTestDTO 빈 객체",
            "objectName": "validTestDTO",
            "field": "innerValidTestDTO",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        }

이런식으로도 나오는데 현재 테스트 중인 프로젝트에서는 위처럼 나오고 있다.
정확한 원인 아직 파악이 안됐지만 중요한 것은 두 형태 모두 그대로 클라이언트에 전달하면 안된다는 것이다.

 

인텔리제이 로그

2024-03-06 11:09:39.588  WARN 16164 --- [nio-8081-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public cohttp://m.study.demo.dto.common.CommonResponseEntity<?> cohttp://m.study.demo.controller.PostController.createPost(com.study.demo.dto.post.PostCreateDto): 
[Field error in object 'postCreateDto' on field 'title': rejected value []; codes [NotBlank.postCreateDto.title,NotBlank.title,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [postCreateDto.title,title]; arguments []; 
default message [title]]; default message [타이틀은 필수입니다.]] ]

 

 

 

 

 2. GlobalException 을 이용한 유효성 예외 처리

 

그러면 해당 예외를 잡아서 알맞게 가공을 해주어야 하는데 가장 좋은 방법이

@ControllerAdvice이다
해당 어노테이션을 붙여 클래스를 생성하고 커스텀 예외를 잡는다고 등록을 해준다.
그러면 해당 에러가 발생했을 때 해당 커스텀 예외 객체를 생성하여 리턴할 수 있다.

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationException(MethodArgumentNotValidException ex) {
        System.err.println("ex :"+ex);
        BindingResult bindingResult = ex.getBindingResult();
        Map<String, String> collect =
                bindingResult.getFieldErrors().stream()
                        .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(collect);
    }   //  @Valid 에러 메세지를 Map 으로 뿌려주는 핸들러

}


그러면 실제 @Valid를 통해 MethodArgumentNotValidException가 발생했을 때 해당 메서드가 실행되며
ResponseEntity에 MethodArgumentNotValidException 정보들을 가져와 값을 넣어준 뒤 리턴해준다.

해당 방법은 응답으로 json을 전달하는 API 기준이며, WAS에 프론트 코드가 포함하는 jsp, 타임리프 등을 사용하면
다른 방법을 통해 해야한다.

왜냐하면 페이지에서 유효성 검증이 안되었다고 에러 페이지로 보내버리지 않는다.
보통 어떤 값이 누락되었다는 메지를 추가한 동일한 페이지를 보여준다.
그렇기 때문에 MethodArgumentNotValidException 예외를 BindingResult 객체로 받은 뒤
가공하여 동일한 페이지로 보내준다.

해당 내용은 추후 정리할 예정이다.

반응형

'Spring' 카테고리의 다른 글

Spring 비지니스 로직 위치  (0) 2024.02.25
Spring 의존 주입 에러 상황  (0) 2024.02.20
Springboot ftp 파일 업로드/다운로드  (0) 2023.08.25
[Spring] CustomReponseEntity  (0) 2023.08.09