Bean Validation

구본식·2023년 1월 20일
2
post-thumbnail

프로젝트 진행중 이전에 학습했던 Bean Validation기능을 적용하면서 복습한 내용을 정리하려고 한다.
아래 github는 김영환 MVC강의를 들으면 간단히 정리해논 내용이다.
https://github.com/BonSik-Koo/Backend_study

1. Bean Validation이란

  • 스프링에서 유효성 검증 로직을 구현하기 위한 사실상 표준
  • 어노테이션 기반으로 동작
  • Bean Validation을 사용하기 위한 의존관계 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'

스프링 MVC는 어떻게 Bean Validator를 사용?
위와 같이 의존관계를 추가하게 되면 스프링부트가 자동으로 Bean Validator를 인지하고 스프링에 통합시킵니다.
Bean ValidationLocalValidatorFactoryBean이라는 Validator를 사용하는데 이 Validator를 자동적으로 스프링 글로벌 Validator로 등록한다.


2. 검증 애노테이션

검증시 @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) 정규식과 일치해야한다. - 문자열 전용


3. 에러 코드

3.1 에러코드

MessageCodesResolver를 통해 메시지 코드가 순서대로 생성된다.

1.애노테이션.오브젝트.필드 (ex. NotBlank.item.itemName)

2.애노테이션.필드 (ex. NotBlank.itemName)

3.애노테이션.타입 (ex. NotBlank.java.lang.String)

4.애노테이션 (ex. NotBlank)

3.2 메시지 등록

error.properties

NotBlank={0} 공백이면 안됩니다.
NotNull = 공백이면 안됩니다.
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}

{0}는 필드명이고 {1},{2}..은 각 애노테이션마다 다르다.
예를 들어
@NotBlank private String itemName이면 오류 메시지가 itemName 공백이면 안됩니다라고 되는것이다.

3.3 BeanValidation 메시지 찾는 순서

  1. 생성된 메시지 코드 순서대로 messageSource에서 메시지 찾기
  2. 애노테이션 message 속성 사용 -> `@NotBlank(message="공백!")
  3. 라이브러리가 제공하는 기본 값 사용 -> 공백일수 없습니다.

4. @Validated groups

4.1 상황

예를 들어 아래와 같은 엔티티가 있다고 가정한다.

@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 Validationgroups기능을 사용
2. Item을 직접 사용하지 않고 등록,수정의 별도의 모델 객체를 만들어서 사용 -> 왠만하면 이방식 사용!

간단히 Bean Validationgroups 사용법을 알아보겠다.

4.2 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) {
 //...
}

보통 폼을 분리하는 방식을 많이 사용하기 때문에 이러한 기능이 있다는것을 알고 넘어가면 될거 같다.


4. @ModelAttribute vs @RequestBody

참고
@ModelAttribute는 HTTP 요청 파라미터(URL 쿼리스트링, POST Form)를 다룰 때 사용
@RequestBody 는 HTTP Body 데이터를 객체로 변환할때 사용. 주로 API JSON 요청시

@ModelAttribute

검증 순서는 아래와 같다.
1. @ModelAttribute의 각각 필드에 타입 변환 (성공시 다음, 실패시 typeMisMatchFieldError추가)
2. Valiator 적용

HTTP 요청 파라미터를 처리하는 @ModelAttribute는 각각의 필드에 단위별로 세밀하게 적용된다.
그래서 특정 필드에 타입이 맞지 않더라도 멈추는것이 아닌 나머지 필드들을 검증을 하게 된다.

@Valid, @Validated 사용 시, 데이터 바인딩과 검증 단계를 거친 후, 바인딩 요청 속성 및 유효성 검증에 대한 포괄적인 예외인 BindException 예외를 발생시킨다.

@RequestBody(HttpMessageConverter)

@ReqeustBOdy는 각각 단위 필드 단위로 적용되는것이 아니라, 전체 객체 단위로 적용된다.

HttpMessageConverter가 작동하여 Item객체로 변환 후, @Valid, @Validated를 적용된다.

HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계가 진행되지 않는것이다. 컨트롤러도 호출되지 않고 Validator도 적용하지 못한다.

@Valid, @Validated 사용 시, body를 HttpMessageConverter를 통해 변환 후, 유효성 검사를 진행하기 때문에 검증 실패 시 MethodArgumentNotValidException 예외를 발생시킨다.

profile
백엔드 개발자를 꿈꾸며 기록중💻

0개의 댓글