김영한 님의 '스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술'을 듣고 적은 글입니다.
Bean Validation은 특정 구현체가 아닌 기술 표준이다. 쉽게 이야기해서 애노테이션과 여러 인터페이스의 모음이다.
Bean Validation을 구현한 기술 중 일반적으로 사용하는 구현체는 하이버네이트 Validator이다.
Bean Validation을 사용하려면 build.gradle에 의존관계를 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
이걸 추가하면 라이브러리가 추가 된다.
jakarta.validation-api : Bean Validation 인터페이스hibernate-validator : 구현체javax.validation 으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스고, org.hibernate.validator로 시작하면 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능이다.
@NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않음@NotNull : null 값 허용 안함@Range(min=1000, max=1000000) : 범위 안의 값이어야 함@Max(9999) : 최대 9999까지 허용스프링부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
LocalValidatorFactoryBean을 글로벌 Validator로 등록한다. 이 검증기는 @NotNull같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 검증기가 적용되어 있기에 @Valid, @Validated 만 적용하면 된다.
검증 오류가 발생하면, FieldError, ObjectError를 생성해 BindingResult에 담아준다.
검증시 @Validated, @Valid 둘 다 사용가능하다. javax.validation.@Valid를 사용하려면 build.gradle 의존관계 추가가 필요하다. @Validated는 스프링 전용 검증 애노테이션이고, @Valid는 자바 표준 검증 애노테이션이다. 둘 중 아무거나 사용해도 되지만 @Validated는 내부에 groups라는 기능을 포함하고 있다.
주의
글로벌 검증기를 직접 등록하면 스프링 부트는 Bean Validator를 글로벌 Validator를 등록하지 않는다. 따라서 애노테이션 기반 빈 검증기가 동작하지 않는다.
@ModelAttribute 각각 필드에 타입 변환 시도typeMismatch로 FieldError 추가-> 즉, 바인딩에 성공한 필드만 Bean Validation이 적용된다.
예)
Bean Validation이 기본으로 제공하는 오류 메시지를 좀 더 자세히 변경하고 싶다면 메시지를 따로 등록해주면 된다.
messageSource에서 메시지 찾기message 속성 사용 -> ex) @NotBlank(message = "공백!{0}")데이터를 등록할 때와 수정할 때는 요구사항이 다를 수 있다.
예를 들어 물건을 등록하는 폼과 수정하는 폼을 생각해보면 비슷한 로직이라고 생각할 수 있겠지만 엄연히 다른 데이터들을 요구한다. 따라서 하나의 검증 로직을 사용하면 당연히 검증 조건의 충돌이 발생할 수 밖에 없다.
Bean Validation의 groups기능을 사용한다.위와 같은 한계를 해결하기 위해 Bean Validation은 groups 라는 기능을 제공한다.
예를 들어 등록 시 검증할 기능과 수정시에 검증할 기능을 각각 그룹으로 나누어 적용할 수 있다.
하지만 실제 실무에서는 groups기능을 잘 사용하지 않는다. 그 이유는 바로 다음에 등장하는 등록용 폼 객체와 수정용 폼 객체를 분리해 사용하기 때문이다.
실무에서는 groups를 잘 사용하지 않는데, 그 이유는 실무에서는 관련 데이터만 받는 게 아닌 그 외의 부가적인 데이터가 넘어오기에 별도의 객체를 만들어 @ModelAttribute로 사용한다. 이것을 폼 데이터에 전달받고, 이후 컨트롤러에 필요한 데이터를 사용한다.
HTML Form -> Item -> Controller -> Item -> Repository
HTML Form -> ItemSaveForm -> Controller -> Item 생성 -> Repository
@Valid, @Validated는 HttpMessageConverter (@RequestBody ) 에도 적용 가능하다.
@RequestBody는 HTTP Body의 데이터를 객체로 변환할 때 사용한다. 주로 API JSON 요청을 다룰 때 사용한다.
HTTP 요청 파라미터를 처리하는 @ModelAttribute는 각각의 필드 단위로 세밀하게 적용된다. 그래서 특정 필드에 타입이 맞지 않는 오류가 발생해도 나머지 필드는 정상처리할 수 있다.
HttpMessageConverter는 @ModelAttribute와 다르게 각각의 필드 단위로 적용되는 것이 아닌 전체 객체 단위러 적용된다.
따라서 메시지 컨버터는 작동이 성공해 해당 객체를 만들어야 @Valid, @Validated가 적용된다.
@ModelAttribute는 필드 단위로 정교하게 바인딩이 적용된다. 특정 필드가 바인딩되지 않아도 나머지 필드는 정상 바인딩되고, 검증기를 사용한 검증도 적용할 수 있다.
@RequestBody는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계가 진행되지 않고 예외가 발생한다. 따라서 컨트롤러도 호출되지 않고 검증기도 적용할 수 없다.