✔️ Validation 이란
Validation은 데이터의 유효성을 검사하는 것으로 Presentation Layer부터 Persistence Layer까지 모든 애플리케이션 계층에서 발생하는 공통 작업입니다.
Spring Bean Validation은 Validator 인터페이스를 사용하여 직접 도메인 객체를 검증하는 방법을 표준화하고 어노테이션을 이용해 선언적으로 표현할 수 있도록 도와줍니다.
이에 대한 구현체로 Hibernate Validator가 있고 Spring Boot는 이 구현체를 기본적으로 지원합니다.
또한 Spring 3부터는 컨트롤러 메서드의 파라미터를 자동으로 검증하는 어노테이션인 @Valid를 제공하며 컨트롤러 메서드에서 검증하는 로직을 완전히 제거해 자동으로 검증할 수 있게 했습니다.
📌 기존의 Validation 방식
//Validator 획득
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
//검사 할 Bean 정의
User user = new User();
user.setWorking(true);
user.setAboutMe("Its all about me!");
user.setAge(50);
//Bean 검사 후 에러 확인
Set<ConstraintViolation<User>> violations = validator.validate(user);
for (ConstraintViolation<User> violation : violations) {
log.error(violation.getMessage());
}
📌 Spring Boot의 Validation 방식
//@Valid 선언 만으로 자동 검사
@RestController
public class UserController {
@PostMapping("/user")
public ResponseEntity<Void> createUser(@RequestBody @Valid UserDTO userDto, BindingResult bindingResult) {
...
}
}
Spring Boot는 ValidationAutoConfiguration을 통해 직접 빈을 추가할 필요 없이 Validator를 사용할 수 있게 해줍니다. LocalValidatorFactoryBean을 기본 Validator로 등록하고 MethodValidationPostProcessor를 자동으로 설정합니다.
📌 Constraints
@Null // null만 혀용합니다.
@NotNull // null을 허용하지 않습니다. "", " "는 허용합니다.
@NotEmpty // null, ""을 허용하지 않습니다. " "는 허용합니다.
@NotBlank // null, "", " " 모두 허용하지 않습니다.
@Email // 이메일 형식을 검사합니다. 다만 ""의 경우를 통과 시킵니다. @Email 보다 아래 나올 @Patten을 통한 정규식 검사를 더 많이 사용합니다.
@Pattern(regexp = ) // 정규식을 검사할 때 사용됩니다.
@Size(min=, max=) // 길이를 제한할 때 사용됩니다.
@Length(min=, max=) // min과 max 사이 값인지 확인합니다.
@Max(value = ) // value 이하의 값을 받을 때 사용됩니다.
@Min(value = ) // value 이상의 값을 받을 때 사용됩니다.
@DecimalMax(value=, inclusive=) // 최대값보다 작은지 확인합니다. inclusive=true인 경우 최대값과 같은 것도 포함합니다.
@DecimalMin(value=, inclusive=) // 최소값보다 큰지 확인합니다. inclusive=true인 경우 최소값과 같은 것도 포함합니다.
@Positive // 값을 양수로 제한합니다.
@PositiveOrZero // 값을 양수와 0만 가능하도록 제한합니다.
@Negative // 값을 음수로 제한합니다.
@NegativeOrZero // 값을 음수와 0만 가능하도록 제한합니다.
@Digits(integer=, fraction=) // 값의 자릿수와 소수 자릿수를 확인합니다.
@Future // 현재보다 미래
@FutureOrPresent // 현재 또는 미래인지 확인합니다.
@Past // 현재보다 과거
@PastOrPresent // 현재 또는 과거인지 확인합니다.
@AssertFalse // false 여부, null은 체크하지 않습니다.
@AssertTrue // true 여부, null은 체크하지 않습니다.
@CreditCardNumber(ignoreNonDigitCharacters=) // 신용카드 번호를 실수했는지 확인합니다. ignoreNonDigitCharacters는 문자를 무시합니다.
@Currency(value=) // 통화 단위가 지정된 통화 단위인지 확인합니다.
@DurationMax(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=) // 지정된 기간보다 크지 않은지 확인합니다. inclusive=true면 같은 날이 허용됩니다.
@DurationMin(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=) // 지정된 기간보다 작지 않은지 확인합니다. inclusive=true면 같은 날이 허용됩니다.
✔️ Spring Bean Validation 사용해보기
📌 build.gradle 라이브러리 추가
//Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
📌 DTO에 Validation 어노테이션 추가
@Getter @Setter
public class UserDTO {
@NotBlank
private String name;
@Email
private String email;
@NotBlank
private String password;
public User toEntity() {
.name(this.name)
.email(this.email)
.password(this.password)
.build();
}
}
만약 DTO 안에 멤버 변수 타입이 Primitive Type이나 String 외에 다른 타입인데 validation하려면
해당 멤버 변수에 @Valid를 붙이고 해당 타입의 클래스에서 다시 validation 어노테이션을 붙인다.
📌 @Valid, @Validated 추가
1. @Validated는 스프링에서 제공하며 그룹 레벨에 사용하고 @Valid는 자바에서 제공하며 메서드 레벨에 사용합니다.
2. @Validated는 Controller뿐만 아니라 다른 스프링 빈에도 사용 가능하고 @Valid는 Controller에서만 동작합니다.
3. 컨테이너 객체(List, Map, ...)를 검사하려면 @Validated를 사용해야 합니다.
4. @Validated는 @Valid를 완전히 포함합니다.
- @RequestParam , @PathVariable 를 사용하는 경우
@Validated
@RestController
public class UserController {
@GetMapping("/users")
public ResponseEntity<Void> users(
@Min(1) @RequestParam(value = "page") int page,
@Min(1) @Max(100) @RequestParam(value = "size") int size) {
return ResponseEntity.noContent().build();
}
}
모든 계층에서 메서드 파라미터나 리턴 값을 검증하기 위해서는 클래스에 @Validated를 붙이면
MethodValidationPostProcessor에 의해 Validation되도록 프록시 객체가 생성됩니다.
이렇게 생성된 프록시를 통해 메서드 파라미터나 리턴 값에 바로 constraints를 적용할 수 있습니다.
- @RequestBody를 사용하는 경우
@PostMapping("/user")
public ResponseEntity<Void> createUser(@RequestBody @Valid UserDTO userDto, BindingResult bindingResult) {
...
}
@RequestBody는 Jackson2HttpMessageConverter가 Request의 Body에 있는 값(JSON)을 DTO로 바인딩하도록 합니다.
JSON 타입이 아닌 경우엔 Query Parameter를 사용하기 위해 Spring의 WebDataBinder를 사용합니다.
Jackson2HttpMessageConverter는 objectMapper를 사용하여 setter가 필요없고 WebDataBinder는 setter가 필요합니다.
이 때 Validation하기 위해서는 @Valid를 붙여 validator가 객체의 유효성을 검사하도록 합니다.
📌 예외 처리
- ConstraintViolationException
@Validated를 사용하는 경우 유효하지 않다면 ConstraintViolationException이 터집니다.
스프링에서는 이 예외를 기본적으로 StatusCode 500으로 처리하기 때문에 다른 코드로 변경하고 싶다면 별도로 변경해야 합니다.
- MethodArgumentNotValidException
@Valid를 사용하는 경우 유효하지 않다면 MethodArgumentNotValidException이 터집니다.
스프링에서 기본적으로 빈으로 등록되는 예외로 ConstraintViolationException과 다르게 StatusCode 400으로 처리하게 됩니다.
- BindingResult
@Validated나 @Valid가 붙은 파라미터 바로 뒤에 BindingResult를 둬서 예외가 터졌을 경우 해당 내용을 확인할 수 있습니다.
BindingResult 파라미터로 예외를 받지 않는다면 StatusCode 400과 함께 BindException이 터지게 되는데 @ExceptionHandler를 통해 잡아 처리할 수 있습니다.
- @ExceptionHandler
@RestController
@Validated
class ValidateParametersController {
// request mapping method omitted
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<>("not valid due to validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
}
컨트롤러 안에 처리할 예외를 지정한 @ExceptionHandler를 붙인 메서드를 만들어 원하는대로 예외를 처리할 수 있습니다.
✔️ Custom Annotation
constraints은 javax.validation.constrains 패키지에 기본적으로 제공되지만 모든 제약조건을 제공하진 않습니다.
필요한 제약조건은 직접 만들어서 사용해야 하며 어노테이션과 validator를 만들어주어야합니다.
- Custom Annotation
@Documented
@Constraint(validatedBy = CustomValidator.class) //담당 validator 지정
@Target({ ElementType.METHOD, ElementType.FIELD }) //타겟 레벨 지정
@Retention(RetentionPolicy.RUNTIME) //유지 범위 지정
public @interface CustomAnnotationName {
String message() default "유효성 검사 실패 시 기본 메시지";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
- Custom Validator
//Validator는 반드시 ConstraintValidator를 구현해야 합니다.
public class CustomValidatorName implements ConstraintValidator<어노테이션이름, 검증할객체타입>{
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
...
}
}
📄 Reference
https://jeong-pro.tistory.com/203
https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/
'📚 Study > Spring' 카테고리의 다른 글
[Spring] Entity, DTO, DAO, VO? (0) | 2022.08.09 |
---|---|
[Spring] JUnit을 통한 TDD (0) | 2022.08.05 |
[Spring] Spring Security + JWT (0) | 2022.08.01 |
[Spring] Swagger? Spring REST Docs? (0) | 2022.07.21 |