저번 포스트에서 Bean Validation
을 제외한 검증 방법들을 살펴보았다. 앞선 방법들로만 검증 코드를 작성한다면 생각보다 검증에 대한 개발 시간과 자원이 많이 소비될 것 같다.
생각해보면 데이터가 빈 값으로 들어왔는지 혹은 최솟값을 넘었는지 최댓값보다 작게 들어왔는지 에 대한 검증이 대부분일 것 같다.
이런 검증 로직을 모든 프로젝트에 적용할 수 있게 공통화하고, 표준화 한 것이 바로 Bean Validation이다.
Bean Validation을 잘 활용하면, 애노테이션 하나로 검증 로직을 매우 편리하게 적용할 수 있다.
bean validation은 기술 표준이다.
일반적으로 사용하는 구현체는 hibernate validator
이다.
프로젝트 생성시 dependency에 validation을 추가하거나
build.gradle에 아래 내용을 추가하면 된다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
사용하고자 하는 도메인 필드 위에 어노테이션을 추가하면 된다.
예를 들어,
@Data
public class Product {
private Long id;
@NotBlank
private String productName;
@NotNull
@Range(min = 10000, max = 1000000)
private Integer price;
@NotNull
@Max(150)
private Integer quantity;
public Item() {}
public Item(String productName, Integer price, Integer quantity) {
this.productName = productName;
this.price = price;
this.quantity = quantity;
}
}
검증 애노테이션
@NotBlank
: 빈값 + 공백만 있는 경우를 허용하지 않는다.
@NotNull
: null 을 허용하지 않는다.
@Range(min = 10000, max = 1000000)
: 범위 안의 값이어야 한다.
@Max(150)
: 최대 150까지 허용한다.
스프링 부트가 spring-boot-starter-validation
라이브러리를 넣으면 자동으로 Bean Validator
를 인지하고 자동으로 글로벌 Validator
로 등록한다.
LocalValidatorFactoryBean
을 글로벌 Validator
로 등록한다. 이 Validator
는 @NotNull
같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 Validator
가 적용되어 있기 때문에, 검증하고자 하는 객체 앞에 @Valid
, @Validated
만 적용하면 된다.
검증 오류가 발생하면, FieldError
, ObjectError
를 생성해서 BindingResult
에 담아준다.
@ModelAttribute
객체의 각 필드에 타입 변환 시도. 실패시 typeMismatch
로 FieldError
추가.Validator
적용.당연히 타입 변환이 안되면 검증 로직을 실행하는게 의미 없어 보인다.
근데 typeMismatch
로 추가된다는게 무슨 말인가? 앞선 포스터에서 언급했던 오류 코드가 typeMismatch라는 것이다. (errors.properties
에 작성한 오류코드.)
Bean Validation
을 통한 오류코드는 어노테이션 이름과 같다. 즉, @NotNull
이 붙은 필드에서 검증 오류가 생기면 그 오류 코드는 NotNull
이 된다.
정확하게는 MessageCodeResolver
에 의해 NotNull.클래스.필드
라는 이름의 오류코드 우선순위가 가장 높고 NotNull.필드
, NotNull.java.lang.타입
, NotNull
순으로 우선순위가 높을 것이다.
errors.properties에
NotNull={0}에는 값이 있어야 합니다.
Range={2}~{1} 범위 확인.
위와 같이 작성했을 때 {0}
에는 필드명이 들어간다. 그리고 Range
에서 {1}
에는 최댓값, {2}
에는 최솟값으로 작성한 값이 들어간다. 언제 넣어준 값? 어노테이션 작성했을 때 넣어준 그 값.
글로벌 Validator
로 등록되는데 만약 새로 추가 등록 시의 검증과 수정 시의 검증을 다르게 하고싶다면 굉장히 난처해진다.
groups
에 인터페이스 추가.@Validated
에 인터페이스 추가. (@Valid
에는 적용불가)예를 들어, 수정 전용 검증을 위해 EditCheck
라는 인터페이스를 새로 생성하고 나면
@NotNull(groups = EditCheck.class)
private 필드
다음과 같이 어노테이션에 추가.
마지막으로
@PostMapping("")
public String aa(@Validated(EditCheck.class) @ModelAttribute 필드 ...)
검증 대상에 추가하면 된다.
실무에서는 주로 이 방법이 사용된다고 한다.
도메인의 클래스에 Bean Validation을 모두 지워주고 폼에서 입력받는 데이터를 따로 클래스로 만들어주면 된다. 앞선 방식과 같이 데이터로 취급하고 각 필드에 원하는 검증 어노테이션을 붙이면 된다. 다만 폼 객체로 입력받았으니 데이터를 추출하고 저장할 때는 도메인의 클래스로 변환하는 추가적인 과정이 있다.
검증에 대해 꽤 깊이 있게 공부해봤다. Bean Validation을 먼저 배웠다면 구체적인 동작 원리를 이해하는게 불가능해 보인다. BindingResult( rejectValue(), reject() ), Validator, MessageCodeResolver, @Validated, 폼 전용 객체 등 유용한 기능들에 대해 알아보았다. hibernate validation에는 email, 신용카드 등 다양한 검증 어노테이션들이 있다고 하니 추가적인 공부를 하는데 많은 도움이 될 것 같다.