모르지 않다는 것은 아는것과 다르다.

Spring

Bean Validation (BindingResult 개념을 먼저 숙지 해야된다.)

채마스 2022. 2. 26. 02:13

Bean Validation (BindingResult 개념을 먼저 숙지 해야된다.)

 

Bean Validation

  • 특정 피리드에 대한 검증 로직은 대부분 빈 값인지 아닌지, 특정 크기를 넘는지 아닌지와 같이 매우 일반적인 로직일 확률이 크다.
  • 그렇기 때문에 이런 검증 로직을 모든 프로세스에 적용할 수 있게 공동화하고, 표준화 한 것이 Bean Validation이다.
  • Bean Validation을 잘 활용하면, 애노테이션 하나로 검증 로직을 매우 편리하게 적용할 수 있다.
  • implementation 'org.springframework.boot:spring-boot-starter-validation' 디펜던시를 추가해줘야한다.
  • 예시로는 아래와 같은 검증 애노테이션이 있다.
      @NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않는다.
      @NotNull : null 을 허용하지 않는다.
      @Range(min = 1000, max = 1000000) : 범위 안의 값이어야 한다. @Max(9999) : 최대 9999까지만 허용한다.
  • 위의 예시중 @NotBlank의 경우에는 javax.validation.constraints.NotNull 인것을 확인할 수 있고, @Range 인 경우에는 org.hibernate.validator.constraints.Range인 것을 확인할 수 있다.
  • javax.validation 으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스이고, org.hibernate.validator 로 시작하면 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능이다.
  • 실무에서 대부분 하이버네이트 validator를 사용하므로 자유롭게 사용해도 된다.
  • 스프링 부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
  • LocalValidatorFactoryBean 을 글로벌 Validator로 등록한다.
  • Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행한다. -> 글로벌 Validator가 적용되어 있기 때문에, @Valid , @Validated 만 적용하면 된다.
  • 검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다.
  • 1차적으로 타입 변환에 성공해서 바인딩에 성공한 필드만 Bean Validation을 적용한다.
    • @ModelAttribute 각각의 필드 타입 변환시도 변환에 성공한 필드만 BeanValidation 적용
  • 예시는 아래와 같다.
      PostMapping("/{itemId}/edit")
      public String edit(@PathVariable Long itemId, @Validated @ModelAttribute Item item, BindingResult bindingResult) {
      //특정 필드 예외가 아닌 전체 예외
      if (item.getPrice() != null && item.getQuantity() != null) {
          int resultPrice = item.getPrice() * item.getQuantity();
          if (resultPrice < 10000) {
              bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
          }
          if (bindingResult.hasErrors()) {
              log.info("errors={}", bindingResult);
              return "validation/v3/editForm";
          }
          itemRepository.update(itemId, item);
          return "redirect:/validation/v3/items/{itemId}";
    }
  • 하이버네이트 Validator 관련 링크
      공식 사이트: http://hibernate.org/validator/
      공식 메뉴얼: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/ 검증 애노테이션 모음: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/ html_single/#validator-defineconstraints-spec





Bean Validation의 한계

  • 객체를 생성할 때와 수정할 때에 validation 처리를 다르게 진행해야할 경우가 있다.
  • 위의 문제를 해결하기 위한 방법으로 BeanValidation groups가 있다.
    • 예시는 아래와 같다.
    • 저장용, 수정용 interface를 생성한다.
      public interface UpdateCheck {
      
      }
      @NotNull(groups = UpdateCheck.class) //수정시에만 적용 
      private Long id;
      
      @NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
      private String itemName;
      
      @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
      @Max(value = 9999, groups = SaveCheck.class) //등록시에만 적용 
      private Integer quantity;
      @PostMapping("/{itemId}/edit")
      public String editV2(@PathVariable Long itemId, @Validated(UpdateCheck.class)
      @ModelAttribute Item item, BindingResult bindingResult) {
      }
    • public interface SaveCheck { }
    • 이 경우에는 @Valid는 지원되지 않고 @Validated만 지원되는 기능이다.
  • 하지만 위의 방법은 복잡하고 지저분해서 잘 사용하지 않는다.
  • 실무에서는 검증하고싶은 데이터를 따로 class 파일로 만들어서 (ex> dto, resource) 를 만들어 검증하는 것이 좋다 -> 사실 생성하고 수정은 완전히 다른 로직이기 때문이다.
  • @PostMapping("/add") public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult) {
  • 위와 같이 @Validated 를 걸어주고 ItemSaveForm을 인자로 넘겨주면 된다.
  • @ModelAttribute("item") 에서 "item"으로 설정을 안해주면 "ItemSaveForm"으로 지정된다.
  • @Valid로 대체할 수 있다.





@ModelAttribute vs @RequestBody

  • 모두 검증을 할 수 있다.
  • 하지만 두가지 방식에는 차이가 있다.
  • 먼저 @ModelAttribute는 필드 단위로 검증을 하기 때문에 잘못된 타입의 필드가 들어오게 되어도 컨드롤러를 타고 들어올 수 있어서 BindingResult 객체에 에러 데이이터가 담기고 설정한 Bean Validation 을 검증할 수 있다.
  • 하지만 @RequestBody를 통한 검증은 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 아예 컨트롤러를 호출하지 않는다 -> 결국 Validator를 적용할 수 없어서 설정한 Bean Validation 을 검증할 수 없다.




REFERENCES

  • 김영한님의 스프링 MVC 2편

'Spring' 카테고리의 다른 글

SpringMVC  (0) 2022.02.26
BindingResult  (0) 2022.02.26
ApplicationRunner,CommandLineRunner  (0) 2022.02.26
API 예외처리  (0) 2022.02.26
Spring AOP  (0) 2022.02.26