본문 바로가기

Dev/Spring Boot

[Validation] Bean Validation 을 이용한 검증

Bean Validation 이란?

어노테이션 형태로 Request Body DTO 객체를 쉽게 검증할 수 있게 해주는 표준 검증 사양

 

사용 방법

라이브러리

  • implementation 'org.springframework.boot:spring-boot-starter-validation'

Dto 단위

  • 필요한 검증 어노테이션 필드에 추가
public class UserNotNull {
	@NotNull(message = "검증 실패 메세지")
	private String name;
}

컨트롤러 단위

  • @Validated 또는 @Valid 어노테이션 추가
@PostMapping("")
public ResponseEntity test(@RequestBody @Valid UserNotNull userDto) {
	[log.info](<http://log.info/>)("userDto: " + userDto);
	return ResponseEntity.ok().body("검증 성공");
}

 

Cf. 검증 실패하는 경우 핸들링

  • Bean Validation 에 실패하면 MethodArgumentNotValidException 발생한다.
  • 위 Exception 의 기본 에러 메세지는 정확히 어떤 필드가 어떤 조건을 충족 못 했는지 알기 어렵다.
  • getBindingResult() 를 통해 세세히 확인할 수 있다.
  • ex. {"min":"must be greater than or equal to 1","max":"must be less than or equal to 1"}
@RestControllerAdvice
public class ErrorHandler {
	@ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex){
      Map<String, String> errors = new HashMap<>();
      ex.getBindingResult().getAllErrors()
              .forEach(c -> errors.put(((FieldError) c).getField(), c.getDefaultMessage()));
      return ResponseEntity.badRequest().body(errors);
  }
}

 

Cf. @Valid, @Validated 차이

  • @Valid
    • 요구 사항 케이스에 따라 검증할 수 없음
    • 모든 필드를 검증할 때 이용
  • @Validated
    • groups 개념으로 위 @Valid의 단점을 보완
    • 특정 group 에 포함된 일부 필드 만을 검증
    • @Validated 를 쓰는 경우 group 을 지정하지 않으면 valid 로 간주

 

어노테이션 종류

1. 숫자 범위

@Min, @Max, @DecimalMin, @DecimalMax

  • @Min(n): n 이상이어야 한다.
  • @Max(n): n 이하여야 한다.
  • @DecimalMin("n"): n 이상이어야 한다.
  • @DecimalMax("n"): n 이하여야 한다.
  • null 도 valid 로 간주함
  • 사용처
    • BigDecimal, BigInteger, CharSequence, byte, short, int, long
    • Double, Float 타입은 rounding error 때문에 사용 불가
  • Cf. @Min, @DecimalMin 차이는?
    • @DecimalMin() 인자에는 int, Long 범위 이상의 값(BigInteger) 넣을 수 있음
    Long value1 = 9999999999999999999L; : 컴파일 에러 O
    BigInteger value = new BigInteger("999999999999999999"); : 컴파일 에러 X
    

@Positive, @PositiveOrZero, @Negative, @NegativeOrZero

  • null 도 valid 로 간주함
  • @Positive: 양수여야 한다. (n > 0)
  • @PositiveOrZero: 0 또는 양수여야 한다. (n >= 0)
  • @Negative: 음수여야 한다. (n < 0)
  • @NegativeOrZero: 0 또는 음수여야 한다. (n <= 0)
  • 사용처
    • BigDecimal, BigInteger, CharSequence, byte, short, int, long, double, float

@Digits

  • @Digits(integer = n, fraction = m)
  • null 도 valid 로 간주함
  • 최대 정수 자리 수 n개, 최대 소수 자리 수 m개
  • 사용처
    • BigDecimal, BigInteger, CharSequence, byte, short, int, long

 

2. 시간

@Past, @PastOrPresent, @Future, @FutureOrPresent (이슈)

  • null 도 valid 로 간주함
  • @Past: Now 보다 과거
  • @PastOrPresent: Now 또는 Now 보다 과거
  • @Future: Now 보다 미래
  • @FutureOrPresent: Now 또는 Now 보다 미래
  • Now 기준: ClockProvider의 가상 머신에 따라 현재 시간을 정의
  • 사용처
    • java.util.Date java.util.Calendar
    • java.time.Instant java.time.LocalDate
    • java.time.LocalDateTime java.time.LocalTime
    • java.time.MonthDay java.time.OffsetDateTime
    • java.time.OffsetTime java.time.Year
    • java.time.YearMonth
    • java.time.ZonedDateTime
    • java.time.chrono.HijrahDate
    • java.time.chrono.JapaneseDate
    • java.time.chrono.MinguoDate
    • java.time.chrono.ThaiBuddhistDate

 

3. Not ~

@NotNull, @NotEmpty, @NotBlank

  • @NotNull
    • null 이 아니어야 한다.
    • "", " " 가능
  • @NotEmpty
    • 길이가 0 이상이고, null 이 아니어야 한다.
    • " " 가능
    • 사용처
      • CharSequence, Collection, Map, Array
  • @NotBlank
    • 공백 문자를 제외한 문자가 1개 이상 있고, null 이 아니어야 한다.
    • " " 불가능
    • 사용처
      • CharSequence

 

4. 패턴

@Size(min = n, max = m)

  • null 도 valid 로 간주함
  • 해당 객체 내용의 길이, 개수를 검증
  • n 이상 m 이하 (디폴트 0 <= x <= 2147483647)
  • 사용처
    • CharSequence: 문자열 길이 / Collection, Map, Array: 원소 길이

@Email (이슈)

  • null 도 valid 로 간주함
  • 문자열 사이(맨 앞, 맨 끝 X)에 @가 존재해야 한다.
  • 사용처
    • CharSequence

@Pattern

  • null 도 valid 로 간주함
  • 지정한 정규식과 대응되는 문자열이어야 한다.
  • 사용처
    • CharSequence

 

5. Boolean

@AssertTrue, @AssertFalse

  • @AssertTrue: true 여야 한다.
  • @AssertFalse: false 여야 한다.
  • 사용처
    • Boolean

 

Custom Validation 만들기

ConstraintValidator 이용

  • Interceptor 단위에서 검증 진행
  • AOP 에러 핸들링 가능

적용 방법

  1. 어노테이션 생성
@Constraint(validatedBy = UniqueEmailValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueEmail {
    String message() default "이미 존재하는 이메일입니다";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}
  1. ConstraintValidator 를 상속 받아 검증 로직 작성
public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String>  {

    @Override
    public void initialize(UniqueEmail constraintAnnotation) {}

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        return email.equals("중복이메일") ? false : true;
    }

}
  1. Dto 객체 필드에 어노테이션 추가
@UniqueEmail
private String alreadyExistEmail;

 

소스 코드

  • 검증 어노테이션 별 테스트 코드 작성

 

Ref.