Bean Validation - 4(groups)

    한계

    수정시 검증 요구사항

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

     

    등록시 기존 요구사항

    • 타입 검증
      • 가격, 수량에 문자가 들어가면 검증 오류 처리
    • 필드 검증
      • 상품명: 필수, 공백X
      • 가격: 1000원 이상, 1백만원 이하
      • 수량: 최대 9999
    • 특정 필드의 범위를 넘어서는 검증
      • 가격 * 수량의 합은 10,000원 이상

    수정시 요구사항

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

    수정 요구사항 적용

    @Data
    public class Item {
        @NotNull // 수정 요구사항
        private Long id;
    
        @NotBlank
        private String itemName;
    
        @NotNull
        @Range(min = 1000, max = 1000000)
        private Integer price;
    
        @NotNull
        //@Max(9999) // 수정 요구사항 추가
        private Integer quantity;
    
        public Item() {
        }
    
        public Item(String itemName, Integer price, Integer quantity) {
            this.itemName = itemName;
            this.price = price;
            this.quantity = quantity;
        }
    }

     

    참고

    현재 구조에서는 수정시 item 의 id 값은 항상 들어있도록 로직이 구성되어 있다. 그래서 검증하지 않아도 된다고 생각할 수 있다. 그런데 HTTP 요청은 언제든지 악의적으로 변경해서 요청할 수 있으므로 서버에서 항상 검증해야 한다. 예를 들어서 HTTP 요청을 변경해서 item 의 id 값을 삭제하고 요청할 수도 있다. 따라서 최종 검증은 서버에서 진행하는 것이 안전하다.

     

    수정을 실행 시 정상 동작을 확인할 수 있다. 그런데 수정은 잘 동작하지만 등록에서 문제가 발생한다.

     

    등록시에는 id 에 값도 없고, quantity 수량 제한 최대 값인 9999도 적용되지 않는 문제가 발생한다.

    등록시 화면이 넘어가지 않으면서 다음과 같은 오류를 볼 수 있다.

    'id': rejected value [null]; 왜냐하면 등록시에는 id 에 값이 없다.

    따라서 @NotNull id 를 적용한 것 때문에 검증에 실패하고 다시 폼 화면으로 넘어온다.

    결국 등록 자체도 불가능하고, 수량 제한도 걸지 못한다.

    결과적으로 item 은 등록과 수정에서 검증 조건의 충돌이 발생하고, 등록과 수정은 같은 BeanValidation 을 적용할 수 없다. 이 문제를 어떻게 해결할 수 있을까?

     

    Bean Validation - groups

    동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법을 알아보자

     

    방법 2가지

    • BeanValidation의 groups기능을 사용한다.
    • Item을 직접 사용하지 않고 ItemSaveForm, ItemUpdateForm 같은 폼 전송을 위한 별도의 모델 객체를 만들어서 사용한다.

    BeanValidation의 groups 사용

    예를 들어 등록시에 검증할 기능과 수정시에 검증할 기능들을 각각 그룹으로 나눠 설정할 수 있다.

     

    groups 적용

    저장용 groups 생성

    package hello.itemservice.domain.item;
    
    public interface SaveCheck {
    }

     

    수정용 groups 생성

    package hello.itemservice.domain.item;
    
    public interface UpdateCheck {
    }

     

    Item - groups 적용

    package hello.itemservice.domain.item;
    import lombok.Data;
    import org.hibernate.validator.constraints.Range;
    import javax.validation.constraints.Max;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
    
    @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;
     
     @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
     @Max(value = 9999, groups = SaveCheck.class) //등록시에만 적용
     private Integer quantity;
     
     public Item() {
     }
     
     public Item(String itemName, Integer price, Integer quantity) {
     	this.itemName = itemName;
     	this.price = price;
     	this.quantity = quantity;
     }
    }

     

    ValidationItemControllerV3 - 저장 로직에 SaveCheck Groups 적용

    @PostMapping("/add")
    public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    		 //...
    }

     

    addItem에 @Validated에 (SaveCheck.class) 적용

     

    ValidationItemControllerV3 - 수정 로직에 UpdateCheck Groups 적용

     

    @PostMapping("/{itemId}/edit")
    public String editV2(@PathVariable Long itemId, @Validated(UpdateCheck.class) @ModelAttribute Item item, BindingResult bindingResult) {
     	//...
    }

    edit에  @Validated에 (UpdateCheck.class) 적용

     

    ※참고 -> @Valid에는 groups를 적용할 수 있는 기능이 없다. 그래서 groups 사용하려면 @Validated를 사용해야 한다.

     

    정리

    groups 기능을 사용해서 등록과 수정시에 각각 다르게 검증을 할 수 있었다. 그런데 groups 기능을 사용하니 Item 은 물론이고, 전반적으로 복잡도가 올라갔다. 사실 groups 기능은 실제 잘 사용되지는 않는데, 실무에서는 주로 다음에 등장하는 등록용 폼 객체와 수정용 폼 객체를 분리해서 사용하기 때문이다.

     

     

     

     

    'SPRING' 카테고리의 다른 글

    Bean Validation - 6 - HTTP 메시지 컨버터  (0) 2023.12.20
    Bean Validation - 5  (1) 2023.12.20
    Bean Validation - 3  (0) 2023.12.20
    Bean Validation - 2(스프링 적용)  (1) 2023.12.20
    Bean Validation - 1  (0) 2023.12.19

    댓글