‘@Valid’는 Bean Validation(빈 검증기)를 이용하여 객체에 대한 조건을 확신하는 어노테이션 입니다.
Spring은 LocalValidatorFactoryBean 어댑터를 활용하여 객체 조건을 확인하게 됩니다.
LocalValidatorFactoryBean 어댑터를 사용하기 위해서는 spring-boot-starter-validation 을 추가해 주면 됩니다.
주요 제약 어노테이션은 다음과 같습니다
- ‘@NotNull’
- null이 아니어야 함
- ‘@NonEmpty’
- 빈 값이 아니어야 함(문자열, 컬렉션 등)
- ‘@Min’ , ‘@Max’
- 숫자의 최소/최대값
- ‘@Email’
- 이메일 형식
- ‘@Pattern’
- 정규 표현식 패턴
public class User {
@NotNull(message = "이름은 빈 값이 아니어야 합니다.")
@Size(min = 2, max = 30, message = "2자에서 30자 사이의 이름을 입력해주세요")
private String name;
@Email(message = "이메일 형식으로 지정해야 합니다.")
private String email;
@Min(value = 18, message = "나이는 18세보다 많아야 합ㅎ니다.")
private int age;
}
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// 유효성 검사를 통과한 경우에만 실행
return ResponseEntity.ok(userService.createUser(user));
}
}
유효성 검증에서 실패 시, MethodArgumentNotValidException이 발생합니다.
이 예외는 ‘@ControllerAdvice’를 활용하여 전역적으로 처리할 수 있습니다.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
}
또한 ‘@Valid’는 중첩된 객체에서도 적용할 수 있습니다.
public class Order {
@Valid
private User user;
}
여기서 말하는 중첩된 객체란, 객체 내에 원시값이 아닌 래퍼 클래스를 의미합니다.
다음 코드로 예시를 보여드리겠습니다.
public class Address {
@NotBlank(message = "Street is required")
private String street;
@NotBlank(message = "City is required")
private String city;
@NotBlank(message = "Zip code is required")
@Pattern(regexp = "\\\\d{5}", message = "Zip code must be 5 digits")
private String zipCode;
}
public class User {
@NotBlank(message = "Name is required")
private String name;
@Email(message = "Email should be valid")
private String email;
@Valid // 중요: 중첩된 객체에 @Valid 적용
private Address address;
}
@RestController
public class UserController {
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// user와 user.address 모두 유효성 검사 수행
return ResponseEntity.ok(userService.createUser(user));
}
}
이는 User 객체 내에 Address 객체가 멤버변수로 있는 것을 확인할 수 있습니다.
이때, Address 안에 객체 검증을 위해 어노테이션(NotBlank, Pattern)등이 존재하는데, User 내에서 Address에 ‘@Valid’ 어노테이션을 사용함으로써 해당 객체에 대한 검증을 진행할 수 있습니다.
또한, 정해진 검증 어노테이션 뿐만 아니라 커스텀 검증 어노테이션을 만들 수 있습니다.
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AdultValidator.class)
public @interface Adult {
String message() default "성인이어야 합니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
이렇게 Adult 라는 어노테이션을 정의해주고 Validator 를 구현합니다.
public class AdultValidator implements ConstraintValidator<Adult, LocalDate> {
@Override
public void initialize(Adult constraintAnnotation) {
}
@Override
public boolean isValid(LocalDate value, ConstraintValidatorContext context) {
if (value == null) {
return true; // null 값은 @NotNull로 처리
}
return ChronoUnit.YEARS.between(value, LocalDate.now()) >= 18;
}
}
해당 Validator를 구현함으로써, Adult 어노테이션을 통해 검증을 할 수 있습니다.
public class User {
@NotBlank(message = "Name is required")
private String name;
@Adult(message = "18세 이상이어야 합니다.")
private LocalDate birthDate;
}
이렇듯 Valid를 통해 객체 검증에 대해 조금 더 간단하고 전역적인 처리가 가능합니다.
Reference
'Spring' 카테고리의 다른 글
Pagination (0) | 2024.07.14 |
---|---|
Bean Lifecycle (0) | 2024.07.07 |
Transaction Propagation (2) | 2024.06.30 |
ThreadLocal (2) | 2024.06.09 |