프로젝트 진행중 이전에 학습했던
Bean Validation
기능을 적용하면서 복습한 내용을 정리하려고 한다.
아래 github는 김영환 MVC강의를 들으면 간단히 정리해논 내용이다.
https://github.com/BonSik-Koo/Backend_study
Bean Validation
을 사용하기 위한 의존관계 추가implementation 'org.springframework.boot:spring-boot-starter-validation'
스프링 MVC는 어떻게 Bean Validator를 사용?
위와 같이 의존관계를 추가하게 되면스프링부트
가 자동으로Bean Validator
를 인지하고 스프링에 통합시킵니다.
Bean Validation
은LocalValidatorFactoryBean
이라는 Validator를 사용하는데 이 Validator를 자동적으로 스프링 글로벌 Validator로 등록한다.
검증시 @Validated
,@Valid
두 어노테이션을 모두 사용가능하다.
@Validated
는 스프링 전용 검증 애노테이션이고 @Valid
는 자바 표준 검증 애노테이션이다.
두가지 모두 동작은 똑같이 작동하지만
@Validated
는 내부에 groups이라는 기능을 포함하고 있는 차이점이 있다.
Bean Validation
에서 사용되는 애노테이션을 간단히 정리하겠다.
@NotBlank
: null + 공백만 있는 경우를 허용하지 않는다. - CharSequence 타입만 허용
@NotEmpty
: null + 빈값을 허용하지 않는다. - CarhSequence(문자열),Colletion,Map,배열 허용
NotNull
: null을 허용하지 않는다. - 모든 타입 허용
Null
: null이어야 한다. - 모든 타입 허용
Max(999)
: value보다 작거나 같아야 한다. - BigInteger,Byte,Short,Integer,Long 타입허용(double, float는 지원 안됨. 하지만 일부 구현체들은 지원할수 도있음)
Min(10)
: value보다 크거나 같아야 한다. - 위와 동일
Range(min=100, max=1000)
: 범위 안의 값이어야한다. - 위와 동일
@Size(min=,max=)
: 요소의 크기가 지정된 경계 포함된 사이에 있어야 한다. - 문자열,Collection,Map,배열 허용(Collection은 사이즈를 대상으로 작동)
@Length(min=,max=)
: 문자열 길이 지정 - 문자열 전용으로 사용됨
@Email
: 이메일 주소 형식인지 검증 - 문자열 전용
@URL
: URL 형식인지 검증 - 문자열 전용
@Pattern(regexp=)
: 정의한(regexp) 정규식과 일치해야한다. - 문자열 전용
MessageCodesResolver
를 통해 메시지 코드가 순서대로 생성된다.
1.애노테이션.오브젝트.필드 (ex. NotBlank.item.itemName)
2.애노테이션.필드 (ex. NotBlank.itemName)
3.애노테이션.타입 (ex. NotBlank.java.lang.String)
4.애노테이션 (ex. NotBlank)
error.properties
NotBlank={0} 공백이면 안됩니다.
NotNull = 공백이면 안됩니다.
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
{0}는 필드명이고 {1},{2}..은 각 애노테이션마다 다르다.
예를 들어
@NotBlank private String itemName
이면 오류 메시지가 itemName 공백이면 안됩니다
라고 되는것이다.
messageSource
에서 메시지 찾기message
속성 사용 -> `@NotBlank(message="공백!")예를 들어 아래와 같은 엔티티가 있다고 가정한다.
@Data
public class Item {
@NotNull //수정 요구사항 추가
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
private Integer quantity;
//...
}
Item
정보를 등록,수정하기 위해서 해당 폼을 요청받는다고 가정해보자.
Item
을 등록할때에는 Item의 속성중 id
는 요청값으로 들어오지 못하게 된다. 알수가 없으니
Item
을 수정할때는 id
속성이 필수적으로 들어와야 하기때문에 검증을 해야된다.
결과적으로 검증 조건 충돌이 발생하고 , 등록과 수정은 같은 Bean Valiation
을 적용하지 못한다.
이를 해결하기 위해 아래와 같은 두가지 방식이 존재한다.
1. Bean Validation
의 groups
기능을 사용
2. Item
을 직접 사용하지 않고 등록,수정의 별도의 모델 객체를 만들어서 사용 -> 왠만하면 이방식 사용!
간단히 Bean Validation
의 groups
사용법을 알아보겠다.
등록시에 검증한 기능과 수정시에 검증할 기능을 각각 그룹을 만들어 적용하는 방식이다.
일단 등록,수정용 그룹을 인터페이스로 아래와 같이 생성한다.
public interface SaveCheck {
}
public interface UpdateCheck {
}
그리고 Item
엔티티에 각 필드에 groups
옵션을 사용하여 각 필드가 속하는 그룹을 명시해준다.
@Data
public class Item {
@NotNull(groups = UpdateCheck.class) //수정시에만 적용
private Long id;
@NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
private String itemName;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Range(min = 1000, max = 1000000, groups = {SaveCheck.class,UpdateCheck.class})
private Integer price
...
마지막으로 등록, 수정 Controller
에서 @Valiated
를 적용할 그룹을 명시해준다.
@PostMapping("/add")
public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//...
}
보통 폼을 분리하는 방식을 많이 사용하기 때문에 이러한 기능이 있다는것을 알고 넘어가면 될거 같다.
참고
@ModelAttribute
는 HTTP 요청 파라미터(URL 쿼리스트링, POST Form)를 다룰 때 사용
@RequestBody
는 HTTP Body 데이터를 객체로 변환할때 사용. 주로 API JSON 요청시
검증 순서는 아래와 같다.
1. @ModelAttribute
의 각각 필드에 타입 변환 (성공시 다음, 실패시 typeMisMatch
로 FieldError
추가)
2. Valiator 적용
HTTP 요청 파라미터를 처리하는 @ModelAttribute
는 각각의 필드에 단위별로 세밀하게 적용된다.
그래서 특정 필드에 타입이 맞지 않더라도 멈추는것이 아닌 나머지 필드들을 검증을 하게 된다.
@Valid, @Validated
사용 시, 데이터 바인딩과 검증 단계를 거친 후, 바인딩 요청 속성 및 유효성 검증에 대한 포괄적인 예외인 BindException
예외를 발생시킨다.
@ReqeustBOdy
는 각각 단위 필드 단위로 적용되는것이 아니라, 전체 객체 단위로 적용된다.
HttpMessageConverter
가 작동하여 Item
객체로 변환 후, @Valid
, @Validated
를 적용된다.
즉 HttpMessageConverter
단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계가 진행되지 않는것이다. 컨트롤러도 호출되지 않고 Validator도 적용하지 못한다.
@Valid, @Validated
사용 시, body를 HttpMessageConverter
를 통해 변환 후, 유효성 검사를 진행하기 때문에 검증 실패 시 MethodArgumentNotValidException
예외를 발생시킨다.