티스토리 뷰

@Valid는 자바 표준 진영에서 지원하는 검증 애노테이션이다.

 

단계별로 검증을 추가하는 식으로 진행해보자

 

# 검증 1단계

userId = 공백 X, 최대 30자

userPassword = 공백 X, 최대 30자

닉네임 = 공백 X, 최대 15자

이메일 = 공백 X

전화번호 = 공백 X, 최대 15자

 

@ToString
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserSignUpRequestDto {
    @Size(max = 30)
    @NotBlank
    private String userId;

    @Size(max=30)
    @NotBlank
    private String userPassword;

    @Size(max = 15)
    @NotBlank
    private String nickname;

    @NotBlank
    private String email;

    @Size(max = 15)
    @NotBlank
    private String phoneNumber;
}
@RequiredArgsConstructor
@RequestMapping("/user")
@Controller
public class UserAccountController {

    private final UserAccountService userAccountService;
    ...
    
    @ResponseBody
    @PostMapping("/sign-up")
    public ResponseEntity<?> signUp(@Valid UserSignUpRequestDto dto, BindingResult bindingResult) {
        userAccountService.create(dto);
        return ResponseEntity.ok("OK");
    }
}

* @NotBlank는 @NotNull을 포함한다 (null, "", white space 모두 걸러냄)

 

대충 동작 흐름은 아래와 같다.

 

0. 클라이언트가 서버로 요청을 보낸다

1. DispatherServlet이 먼저 값을 받는다

2. ArgumentResolver를 이용해 UserSignupResuestDto 파라미터를 처리한다

  - 먼저 클라이언트로부터 받은 값을 객체에 바인딩 시킨다.

  - 이때 필드의 타입이 맞지 않는다면 typeMismatch를 발생시키고, 해당 필드에 대한 검증은 더이상 수행되지 않는다!

 

3. 필드가 객체 바인딩에 성공했다면 이제 각 필드에 붙은 애노테이션을 이용해 Validator를 적용한다.

  - 검증에 실패했다면 BindingResult에 필드에러를 추가한다

 

4. BindingResult가 에러를 가지고 있다면 예외를 반환시킨다

  - @RequestBody의 경우 MethodArgumentNotValidException을 반환한다. 

  - @ModelAttribute의 경우 BindException을 반환한다

  (MethodArgumentNotValidException은 BindException을 extends 하고있다)

 

 

이 예외를 @ExceptionHandler와 @RestControllerAdvice를 이용해 잡아서 규격화된 응답으로 반환해줄 수 있다.

@Builder
@Data
public class ErrorResult {
    private String path; // 요쳥 경로
    private LocalDateTime timestamp; // 에러 발생 시각
    private int status; // 에러 코드
    private String error; // 에러명 (ex: Bad Request, Forbidden...)
    private List<ErrorDetail> errors; // 각 필드 에러 정보
    private String message; // 에러 메세지
}
@Builder
@Data
public class ErrorDetail {
    private String field; // 에러 발생 필드 이름
    private String code; // 어떤 타입의 에러인지 (ex: Size, NotBlank...)
    private String message; // 에러 메세지

    public static ErrorDetail from(FieldError e) {
        return ErrorDetail.builder()
                .field(e.getField())
                .code(e.getCode())
                .message(e.getMessage())
                .build();
    }
}
@Slf4j
@RequiredArgsConstructor
@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException exception, HttpServletRequest request) {
        log.error("[MethodArgumentNotValidException] handle -> {}", exception.getObjectName());

        // 필드 에러 정보 모음
        List<ErrorDetail> fieldErrorList = exception.getFieldErrors().stream()
                .map(error -> ErrorDetail.from(error))
                .collect(Collectors.toList());

        // 객체 반환
        return ErrorResult.builder()
                .timestamp(LocalDateTime.now())
                .status(HttpStatus.BAD_REQUEST.value())
                .error(HttpStatus.BAD_REQUEST.getReasonPhrase())
                .errors(fieldErrorList)
                .message(exception.getMessage())
                .path(request.getRequestURI()).build();
    }
	...
 }

그럼 이렇게 응답이 내가 원하는 대로 이쁘게 만들어져서 나온다.

 

 

# 참고 사이트

- https://kdhyo98.tistory.com/m/80

- https://jyami.tistory.com/m/55

- https://wildeveloperetrain.tistory.com/m/158

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함