[스프링 MVC 2편] 5. 검증 2 - Bean Validation

조은지·2023년 8월 15일

Bean Validation

  • 검증 로직의 기술 표준, 검증 애노테이션과 여러 인터페이스의 모음이다.

  • implementation 'org.springframework.boot:spring-boot-starter-validation'

  • 의존 관계를 추가하여 사용할 수 있다.



Bean Validation 애노테이션의 적용

  • Item 데이터 객체에 검증 애노테이션을 적용할 수 있다.

검증 애노테이션 예)

  • @NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않는다.
  • @NotNull : null 을 허용하지 않는다.
  • @Range(min = 1000, max = 1000000) : 범위 안의 값이어야 한다.
  • @Max(9999) : 최대 9999까지만 허용한다.

스프링 MVC는 어떻게 Bean Validator를 사용?

  • 스프링 부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
  • LocalValidatorFactoryBean 을 글로벌 Validator로 등록하기 때문에 @Valid, @Validated 만 적용하면 된다.
  • Validator@NotNull 같은 애노테이션을 보고 검증을 수행한다.
  • 검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다.

바인딩에 성공한 필드만 Bean Validation을 적용

BeanValidator는 바인딩에 실패한 필드는 BeanValidation을 적용하지 않는다

  • price 에 문자 "A" 입력 => "A"를 숫자 타입 변환 시도 실패 => typeMismatch FieldError 추가 => price 필드는 BeanValidation 적용 X


Bean Validation - 에러 코드

Bean Validation이 기본으로 제공하는 오류 메시지를 변경하고 싶은 경우? => 해당 코드를 errors.properties에 등록한다.


예시 ) NotBlank의 오류 코드
(MessageCodesResolver 를 통해 다양한 메시지 코드가 순서대로 생성된다.)

@NotBlank
NotBlank.item.itemName
NotBlank.itemName
NotBlank.java.lang.String
NotBlank

BeanValidation 메시지 찾는 순서

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


Bean Validation - 오브젝트 오류

특정 필드가 아닌 해당 오브젝트 관련 오류는 어떻게 해결?

1. @ScriptAssert() 사용

검증하고자 하는 클래스 위에 애노테이션을 작성한다.

문제점

  • 제약이 많고 복잡하다.
  • 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우도 있다.

2. 자바 코드로 작성

실무에서는 컨트롤러 단에 직접 코드를 추가해 검증하는 방식을 많이 쓴다.

@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult 
bindingResult, RedirectAttributes redirectAttributes) {
  • BindingResult를 추가하여 직접 ObjectError를 내보낸다.


Bean Validation - 한계

데이터를 등록할 때와 수정할 때는 요구사항이 다를 수 있다.

수정 시 요구사항

  • 등록시에는 id 에 값이 없어도 되지만, 수정시에는 id 값이 필수이다
  • 등록시에는 quantity 수량을 최대 9999까지 등록할 수 있지만 수정시에는 수량을 무제한으로 변경할 수 있다

해결방법 2가지

  1. BeanValiation의 groups 기능을 이용한다.
  2. Item을 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm 같은 폼 전송을 위한 별도의 모델을 사용한다.


Bean Validation - Groups

1. groups 적용

인터페이스를 생성하여 저장용 그룹과 수정용 그룹을 구분한다.

2. Item- groups 적용

public class Item {
    @NotNull(groups = UpdateCheck.class) //수정시에만 적용
    private Long id;
 	
    @NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
    private String itemName;
    

3. Controller - @Validated 에 적용할 group을 명시



Form 전송 객체 분리

실제로 많이 사용하는 방식

폼 데이터 전달을 위한 별도의 객체 사용

HTML Form -> ItemSaveForm -> Controller -> Item 생성 -> Repository

  • 전송하는 폼 데이터가 복잡해도 거기에 맞춘 별도의 폼 객체를 사용해서 데이터를 전달 받을 수 있다.

폼 데이터 전달을 위한 별도의 객체를 사용하고, 등록, 수정용 폼 객체를 나누면 등록, 수정이 완전히 분리되기 때문에 groups 를 적용할 일은 드물다



Bean Validation - HTTP 메시지 컨버터

@Valid , @Validated 는 HttpMessageConverter ( @RequestBody )에도 적용할 수 있다.

API의 경우 3가지 결과가 발생

  • 성공

  • 실패 요청 : JSON을 객체로 생성하는 것 자체가 실패함

  • 검증 오류 요청 : JSON을 객체로 생성하는 것은 성공했고, 검증에서 실패함


2번째 - JSON을 객체로 생성하는데 실패를 보면

타입 오류의 경우 HttpMessageConverter 에서 요청 JSON을 ItemSaveForm 객체로 생성하는데 실패한다.


@ModelAttribute vs @RequestBody

  • @ModelAttribute 는 필드 단위로 정교하게 바인딩이 적용된다. 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator를 사용한 검증도 적용할 수 있다.
  • @RequestBody 는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다. 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.


개인 질문 ) 타입 에러 미리 정리

-스프링에서는 예외 처리의 순서가

컨트롤러(예외발생) -> 인터셉터 -> 서블릿(디스패처 서블릿) -> 필터 -> WAS(톰캣)

의 순으로 된다

WAS까지 해당 예외에 대한 처리가 없으면 기본 오류 화면인 WhiteLabelError와 같은 페이지를 띄운다.

=> 타입에러에 대한 것도 처리를 해줄려면 인터셉터 같은 곳에서 오류 응답을 따로 내보내는 등의 처리를 해줘야 한다. (강의 8,9강)

0개의 댓글