검증 기능을 매번 코드로 작성하는 방식 대신 애노테이션으로 대체하자
Bean Validation은 특정한 구현제가 아닌 검증 기술 표준이다.
의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
validation 의존성을 추가하면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
스프링부트는 LocalValidatorFactoryBean
을 글로번 Validatior로 등록하는데 이것은 어노테이션을 보고 검증을 수행하는 검증기 이다.
검증 오류가 발생하면 FieldError, ObjectError를 만들어 BindingResult에 넣어준다.
(만약 스프링에 글로벌 검증기를 등록한다면 LocalValidatorFactoryBean을 등록하지 않는다.)
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000,max = 1000000)
private Integer price;
@NotNull @Max(9999)
private Integer quantity;
...
}
검증 순서
-> 바인딩에 성공한 필드만 BeanValidation 적용
(바인딩에 실패한 필드는 BeanValidation을 적용하지 않는다, 의미가 없음)
@NotBlank 의 오류코드를 기반으로 MessageCodesResolver를 통해 메시지 코드가 순서대로 생성된다.
NotBlank.item.itemName
NotBlank.itemName
NotBlank.java.lang.String
NotBlank
따라서 앞서 배운 과정대로 한다면 오류메시지를 재정의 할 수 있다.
@ScriptAssert(lang = "javascript",script = "_this.price*_this.quantity >= 10000")
간단한 경우 위의 애노테이션으로 해결할 수있다.
하지만 제약사항이 너무 많기 때문에 ObjectError 같은 경우 전처럼 자바코드를 사용하는
bindingResult.reject()
를 사용하는 것이 더 낫다.
상품 등록 시의 검증 로직과 수정 시의 검증 로직이 다를 떄는 어떻게 해야할까?
-> Groups 기능을 사용한다.
Groups 기능은 @Validated를 사용해야 한다. 하지만 이 기능이 복잡하고 코드도 길어지기 때문에 실무에선 Groups 대신 각각의 Form객체를 분리시켜 사용한다.
@RequestBody에도 @Valid를 사용할 수 있다.
@ModelAttribute의 ArgumentResolver는 쿼리파라미터의 필드 하나하나 바인딩을 수행하기 때문에 특정 필드가 실패하더라도 나머지는 정상적으로 잘 담겨서 컨트롤러로 넘겨짐
@RequestBody의 HttpMessageConverter는 JSON 전체를 객체로 반환하기 때문에 특정 필드만 실패해도 바로 400에러를 뱉는다.