Spring Boot Web의 여러가지 기능-(4)

개발세발·2024년 1월 23일

Validation

  • 전송받은 데이터에 대해서 검증 과정을 거쳐야 함.
  • 그러나 이는 데이터의 길이가 길어질수록 검증 로직도 길어지게 되는 문제가 발생
  • 스프링 부트에서는 spring-boot-starter-validation을 dependency에 주입하여 사용.

validation을 사용하는 이유

  • 유효성 검증하는 코드의 길이가 너무 길다.
  • service logic에 대해서 방해가 된다.
  • 흩어져 있는 경우 어디서 검증 되었는지 찾기 힘들다.
  • 검증 로직이 변경되는 경우 테스트 코드 등, 전체 로직이 흔들릴 수 있다.

여러 기능들

  • @Size: 문자의 길이 측정
  • @NotNull: null 불가
  • @NotEmpty: null, 빈 문자열 불가
  • @NotBlank: null, 빈 문자열, 공백 불가
  • @Pattern: 정규식을 적용할 때
  • @Max: 최대
  • @Min: 최소
  • @AssertTrue/False: 별도의 Logic 적용
  • @Valid: 오브젝트의 validation을 실행시킥겠다.
  • @Past: 과거 날짜
  • @PastOrPresent: 오늘이나 과거
  • @Future: 미래
  • @FutureOrPresent: 오늘이나 미래

적용

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

    @PostMapping("")
    public Api<UserRegisterRequest> register(

        @Valid
        @RequestBody
        Api<UserRegisterRequest> userRegisterRequest
    ){
        log.info("init: {}", userRegisterRequest);
        var body = userRegisterRequest.getData();
        return Api.<UserRegisterRequest>builder()
                .resultCode(String.valueOf(HttpStatus.OK.value()))
                .resultMessage(HttpStatus.OK.getReasonPhrase())
                .data(body)
                .build();
    }
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRegisterRequest {
    //@NotNull //null이면 안됨.
    //@NotEmpty //null이면 안되고,Empty(공백)도 안됨.
    //@NotBlank //null도 안되고, 공백도 안되고, 공백이 포함돼도 안됨.
    private String name;

    private String nickName;

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

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

    @Email
    private String email;

    @PhoneNumber
    private String phonenumber;

    @FutureOrPresent
    private LocalDateTime registerAt;

    @AssertTrue(message = "name or nickname 은 존재해야 합니다.")
    public boolean isNameCheck(){
        if(Objects.nonNull((name)) && !name.isBlank()){
            return true;
        }
        if(Objects.nonNull((nickName)) && !nickName.isBlank()){
            return true;
        }
        return false;
    }

}
  • 각각의 데이터에 validation에 대한 기준을 어노테이션으로 지정해주고, @Valid를 통해 들어오는 데이터에 대한 validation을 데이터에 붙은 어노테이션을 기반으로 진행하도록 설정해준다.
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Api<T> {

    private String resultCode;
    private String resultMessage;

    @Valid
    private T data;

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

    }
}
  • 클라이언트에게도 해당 에러에 대한 정보를 표시할 수 있도록 model 클래스를 하나 만들어준다.

  • data에 대한 validation도 검증을 해주어야 한다. data에 실질적인 값이 들어가기 때문에

@Slf4j
@RestControllerAdvice
public class ValidationExceptinHandler {

    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public ResponseEntity<Api> ValidationException(
            MethodArgumentNotValidException exception
    ){
        log.error("", exception);
        var errorMessageList = exception.getFieldErrors().stream()
                .map(it->{
                    var format = "%s : { %s } 은 %s";
                    var message = String.format(format, it.getField(), it.getRejectedValue(), it.getDefaultMessage());
                    return message;
                }).collect(Collectors.toList());
        var error = Api.Error
                .builder()
                .errorMessage(errorMessageList)
                .build();
        var errorResponse = Api
                .builder()
                .resultCode(String.valueOf(HttpStatus.BAD_REQUEST.value()))
                .resultMessage(HttpStatus.BAD_REQUEST.getReasonPhrase())
                .error(error)
                .build();

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(errorResponse);
    }
}
  • ValidationExceptinHandler클래스를 만들어서, 각 예외에 대한 조치를 지정해준다.
  • 이렇게 ExceptionHandler를 만들어 주지 않으면 컨트롤러에서 로직을 구현해야 하는데, 컨트롤러가 복잡해지고, 비즈니스 로직을 해치는 문제가 발생한다.

0개의 댓글