Bean Validation이란?
- 특정한 구현체가 아니라 Bean Validation 2.0(JSR-380)이라는 기술 표준이다.
- 검증 애노테이션과 여러 인터페이스의 모음이다.
- JPA가 기술 표준이고 그 구현체로 하이버네이트가 있는 것과 같다.
- Bean Validation을 구현한 기술중에 일반적으로 사용하는 구현체는 하이버네이트 Validator이다.
- 이름이 하이버네이트여서 그렇지 실제로 ORM과는 관련은 없다.
- 하이버네이트 관련 링크
Bean Validation 의존성
implementation 'org.springframework.boot:spring-boot-starter-validation'
- Jakarta Bean Validation
- jakarta.validation-api : Bean Validation 인터페이스
- hibernate-validator 구현체
Bean Validation 사용해보기
public class Item {
private Long id;
@NotBlank
private String iteamName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity
}
- javax.validation.constraints.NotNull
- org.hibernate.validator.consraints.Range
- javax.validation으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스이다.
- 하이버네이트로 시작하면 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능이다.
스프링 MVC는 어떻게 Bean Validator를 사용할까?
- 스프링 부트가 spring-boot-stater-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
- 스프링 부트는 자동으로 글로벌 Validator를 등록한다.
- LocalValidatorFactoryBean을 글로벌 Validator로 등록한다.
- 이 Validator는 NotNull과 같은 애노테이션을 보고 검증을 수행한다.
- 이렇게 글로벌 Validator가 적용되어 있지 않기 때문에 Valid, Valiated애노테이션만 적용하면 된다.
검증이 일어나는 순서
- ModelAttribute 각각의 필드에 타입변환을 시도한다.
- 성공하면 다음으로
- 실패하면 typeMismatch로 FieldError로 추가한다.
- 바인딩에 성공한 필드만 Bean Validation을 적용한다.
- 바인딩에 실패한 필드는 적용하지 않는다.
- 생각해보면 타입변환에 성공해서 바인딩부터 성공을 해야 Validation 적용의 의미가 생긴다.
- 일단 모델 객체에 바인딩 받는 값이 정상으로 들어와야 검증도 의미가 생긴다.
- 예시)
- ItemName에 문자 A 입력 > 타입변환 성공 > Validator 적용 > price 에 문자 “A”입력 > 타입변환 실패 > 타입 미스매치 필드에러 추가 > price는 Validator 적용 X
- NotBlank라는 오류코드를 기반으로 MessageCodesResolver를 통해 다양한 메시지 코드가 순서대로 생성된다.
BeanValidation 메시지 찾는 순서는 어떻게 될까?
- 생성된 메시지 코드 순서대로 messageSource에서 메시지를 찾는다.
- 애노테이션의 message 속성을 사용 → NotBlank(message = “공백! {0}”)
- 라이브러리가 제공하는 기본 값 사용 → 공백일 수 없습니다.
필드말고 오브젝트에 대한 오류는 어떻게 처리할까?
@ScriptAssert(lang="javascript", script="_this.price * _this.quantity >= 10000")
public class Item{
~~~
}
- 메시지코드
- ScriptAssert.item
- ScriptAssert
- 하지만 위의 방법은 제약이 많고 복잡하다는 단점이 존재한다. 또한 실무에서 검증 기능이 해당 객체의 범위를 넘어서는 경우도 종종 발생하는데 그런 경우 대응이 어렵다.
- 따라서 오브젝트 오류는 직접 자바 코드로 작성하는것을 권장한다.
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult
bindingResult, RedirectAttributes redirectAttributes) {
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000,
resultPrice}, null);
}
}