백엔드 공부하기/TIL

230829 TIL : [Spring Boot] Validation 처리

개발중인 감자 2023. 8. 30. 01:46

📌 230829 TIL : [Spring Boot] Validation 처리 


1. Validation?

- Spring-boot-starter-validation 통해 처리한다. 

- 데이터에 대한 유효성 처리를 진행할 수 있다.

- 예를 들어, 데이터가 빈값이거나 공백값이면 안되게 유효성 처리를 진행한다. 

- @Valid 혹은 @Validated 어노테이션을 통해 유효성을 검증한다. 

- 만약, 유효성 검증에 실패했다면, MethodArgumentNotValidException 에러 발생한다.

- 어노테이션을 사용함으로써 코드의 가독성을 높일 수 있다. 

 

 

2. 종류

@Size : 문자열의 길이 측정 (not int type)
@NotNull : null x
@NotEmpty : null x, “” x
@NotBlank : null “”, “ “(공백) x
@Pattern : 정규식 적용
@Max, @Min : 최댓값/최솟값
@AssertTrue/False : 별도의 logic 적용
@Vaild : 해당 logic validation 진행
= 날짜 관련 =
@Past : 과거 날짜 체크
@PastOrPresent : 오늘이거나 과거 날짜 체크
@Future : 미래 날짜 체크
@FutureOfPresent : 오늘이거나 미래 날짜 체크

 

3. 방법 

 

1) gradle에 의존성 추가

 

2) model에 가서 유효성 검증하고자 하는 변수에 어노테이션 붙이기.

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRegisterRequest {

    private String name;
    private String nickName;

    @NotBlank
    @Size(min = 1, max = 12)
    private String password;

    @NotNull
    @Min(1)
    @Max(100)
    private Integer age;

    @Email
    private String email;

    @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "휴대폰 번호 양식에 맞지 않습니다.")
    private String phoneNumber;

    @FutureOrPresent //현재 혹은 미래.
    private LocalDateTime registerAt;

    @AssertTrue(message = "name 또는 nickName은 반드시 1개가 존재해야합니다. ")
    public boolean isNameCheck() {
        if (Objects.nonNull(name) && !name.isBlank()) return true;
        if (Objects.nonNull(nickName) && !nickName.isBlank()) return true;
        return false;
    }

}

- @AssertTrue 를 통해 name 혹은 nickName 둘 중 하나만 존재해도 true를 반환하도록 해주는 검증을 구현했다.

 

 

3) 에러 메세지를 담을 Api 클래스 

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Api<T> {
    @Valid //유효성 검사 진행
    private T data;
    
    private String resCode, resMsg;
    private Error error;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
    public static class Error {
        private List<String> errorMsg;
    }

}

- data 변수의 유효성 검사를 진행하여 에러가 발생하면 그 에러 메세지를 담을 에러 클래스를 내부적으로 선언해주었다. 

 

 

4) MethodArgumentNotValidException 에러를 처리할 ExceptionHandler 클래스 구현.

- 해당 클래스를 구현함으로써, 컨트롤러 클래스에서 에러가 발생하면 여기서 처리하도록 해준다. 

package com.example.validation.exception;

@RestControllerAdvice
@Slf4j
public class ValidationExceptionHandler {

    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public ResponseEntity<Api> validationException(
        MethodArgumentNotValidException exception
    ) {
        log.error("", exception);
        var errorMsgList = exception.getFieldErrors().stream()
                .map(it-> {
                    var format = "%s : { %s } 은 %s";
                    var msg = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
                    return msg;
                }).collect(Collectors.toList());

        var error = Api.Error.builder().errorMsg(errorMsgList).build();

        var errorResponse = Api.builder()
                .resCode(String.valueOf(HttpStatus.BAD_REQUEST))
                .resMsg(HttpStatus.BAD_REQUEST.getReasonPhrase())
                .error(error)
                .build();

        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);

    }

}

 

 

5) Controller 구현 

- 여기서는 검증에 통과한 값들만 처리를 한다. 

- 코드는 더보기란

더보기
@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserApiController {

    @PostMapping ("")
    public Api<UserRegisterRequest> register(
            @Valid
            @RequestBody
            Api<UserRegisterRequest> userRegisterRequest
    ) {
        log.info("init : {}", userRegisterRequest);

        /*
        검증에 위배되는 값을 입력시 MethodArgumentNotValidException 에러 발생.
        -> exceptionHandler 처리.
         */

        var body = userRegisterRequest.getData();
        Api<UserRegisterRequest> response = Api.<UserRegisterRequest>builder()
                .resCode(String.valueOf(HttpStatus.OK.value()))
                .resMsg(HttpStatus.OK.name())
                .data(body).build();

        return response;
    }
    
}

 

📝 실행 결과

 

<보낸 요청 Body>

 

<결과>

 

<콘솔>