스프링 Validation은 데이터를 표현하는 객체를 검증하는 프로세스에요. 주로 Controller 요청 데이터 파라미터에 @Valid 어노테이션을 적용해 검증하지만 서비스 내에서나 데이터베이스 쪽에서도 사용 가능해요.
데이터 검증은 객체의 각 필드에 제약 조건 어노테이션을 명시하고 해당 필드가 그 제약 조건에 맞는지 확인해요.
스프링 Validation이 기본적으로 제공하는 제약 조건 어노테이션은 다음과 같아요.
@Null: 필드 값이null이어야 합니다.@NotNull: 필드 값이null이 아니어야 합니다.@NotEmpty: 필드 값이null이거나 빈 문자열("")이 아니어야 합니다. 주로 문자열, 컬렉션에 사용됩니다.@NotBlank: 필드 값이null이거나 빈 문자열("")이거나 공백(" ")으로만 이루어진 문자열이 아니어야 합니다. 주로 문자열에 사용됩니다.@Size: 필드 값의 길이(문자열, 배열) 또는 크기(컬렉션)가 지정된 범위 내에 있어야 합니다.@Min: 필드 값이 지정된 최솟값보다 크거나 같아야 합니다.@Max: 필드 값이 지정된 최댓값보다 작거나 같아야 합니다.@DecimalMin: 필드 값이 지정된 최솟값보다 크거나 같아야 합니다 (문자열 형태).@DecimalMax: 필드 값이 지정된 최댓값보다 작거나 같아야 합니다 (문자열 형태).@Digits: 필드 값이 지정된 정수 자릿수와 소수 자릿수 내에 있어야 합니다.@Pattern: 필드 값이 주어진 정규 표현식과 일치해야 합니다.@Past: 필드 값이 현재 시간보다 과거여야 합니다.@PastOrPresent: 필드 값이 현재 시간보다 과거이거나 현재 시간이어야 합니다.@Future: 필드 값이 현재 시간보다 미래여야 합니다.@FutureOrPresent: 필드 값이 현재 시간보다 미래이거나 현재 시간이어야 합니다.
이 어노테이션은 여러 개를 복수로 적용할 수 있어요. 사용 예시는 다음과 같습니다.
public class User {
@NotNull(message = "이름은 필수입니다.")
@Size(min = 2, max = 10, message = "이름은 2자 이상 10자 이하이어야 합니다.")
private String name;
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
@Min(value = 18, message = "18세 이상만 가입 가능합니다.")
private int age;
...
}
만약 위 제약 조건 외에 다른 조건이 필요하다면 사용자 정의 제약 조건을 정의할 수도 있어요.
사용자 정의 제약 조건은 사용자 정의 어노테이션과 검증 클래스를 정의하고 일반적인 제약 조건 어노테이션과 동일하게 사용하면 돼요.
먼저 사용자 정의 제약 조건 어노테이션을 정의해요. 아래 예시는 휴대폰 번호 형식 제약 조건을 정의하는 예시에요.
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD}) // 어노테이션을 적용할 대상 (필드)
@Retention(RetentionPolicy.RUNTIME) // 어노테이션 유지 기간 (런타임까지)
@Constraint(validatedBy = {PhoneNumberValidator.class}) // 유효성 검증 로직을 가진 클래스 지정
public @interface PhoneNumber {
String message() default "유효하지 않은 전화번호 형식입니다."; // 유효성 검증 실패 시 메시지
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Target을 통해 어노테이션을 적용할 대상을 지정하고 @Retention 어노테이션 유지 기간을 지정합니다.
@Constraint로 실제 유효성 검증 로직을 가진 클래스를 지정해요.
message(), groups(), payload() 추상 메소드들은 사용자 정의 제약 조건 정의 시 필수로 포함해야하는 인터페이스로 처음 예시의 message = "이름은 필수입니다."와 같은 기능이에요.
이제 실제 유효성을 검증하는 클래스를 정의해요.
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
@Override
public void initialize(PhoneNumber constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // null 값은 유효하다고 가정 (필요에 따라 변경 가능)
}
// 전화번호 유효성 검증 로직 (예: 정규 표현식 사용)
return value.matches("^01(?:0|1|[6-9])-\\d{3,4}-\\d{4}$");
}
}
유효성 검증 클래스는 ConstraintValidator 인터페이스를 구현해요. 해당 인터페이스의 제네릭 타입은 <어노테이션 타입, 필드 타입>이에요 위 코드에서는 PhoneNumber 어노테이션 구현체를 의미하고 String 필드에 적용되는 제약 조건임을 나타내요.
그리고 isValid 메소드에서 해당 필드 값에 대한 검증을 수행해요. 위의 유효성 검증 클래스는 휴대폰 번호 패턴("^01(?:0|1|[6-9])-\\d{3,4}-\\d{4}$")을 검증합니다.
이 어노테이션의 적용은 다음과 같아요.
public class User {
@PhoneNumber(message = "전화번호 형식이 올바르지 않습니다.")
private String phoneNumber;
...
}
다른 일반적인 어노테이션과 같은 방법으로 사용하면 돼요.
스프링 Validation은 유효성 검증을 활성화하거나 명시적으로 데이터 검증을 수행해야 하는데요. 명시적인 방법은 스프링 Validation 구현체인 hibernate의 방법이니 이 장에서는 다루지 않아요. 스프링에서는 @Validated 어노테이션을 통해 데이터 유효성 검증을 활성화해요.
그 다음 유효성 검증 대상을 지정해주어야 하는데 파라미터의 @Valid 어노테이션을 적용해서 유효성 검증을 수행할 수 있어요. 다음은 두 어노테이션이 적용된 예시에요.
@RestController
@Validated // 클래스 레벨 유효성 검증 활성화
public class UserController {
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody @Valid User user) {
// 유효성 검증 통과 시 처리
return new ResponseEntity<>("User created", HttpStatus.OK);
}
...
}
/users API를 제공하는 Controller 클래스에 @Validated 어노테이션을 적용해 유효성 검증을 활성화하고 User user 파라미터에 @Valid 어노테이션을 통해 유효성 검증 대상을 지저해주었어요.
데이터의 유효성 검증을 프레임워크 레벨에서 수행하기 때문에 유효성 검증 실패를 처리하는 방식이 일반적인 Java와는 다른데요. Controller의 BindingResult 파라미터를 추가해 처리하는 방법과 @ExceptionHanlder 어노테이션을 이용하는 방법이 있어요. 여기서는 많이 사용하는 @ExceptionHanlder 방식을 소개해 드릴게요.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
// 유효성 검증 실패 시 처리
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
스프링 환경설정 어노테이션 중 하나인 @ControllerAdvice를 사용해 애플리케이션에서 catch되지 않은 예외를 처리할 수 있어요. (유효성 검증 실패 오류 뿐만 아니라 다양하게 이용 가능해요)
유효성 검증 실패 시 스프링은 MethodArgumentNotValidException 예외를 발생시켜요. @ExceptionHandler(MethodArgumentNotValidException.class)를 통해 유효성 검증 실패 시 처리하고 올바른 오류 응답을 줄 수 있어요.