오봉이와 함께하는 개발 블로그

스프링 MVC 2 - BeanValidation 한계, groups 본문

BE/Spring

스프링 MVC 2 - BeanValidation 한계, groups

오봉봉이 2022. 8. 24. 23:47
728x90

Bean Validation - 한계

수정시 검증 요구사항

등록시 기존 요구사항

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

수정시 요구사항

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

수정 요구사항 적용

수정시에는 Item에서 id값이 필수이고, quantity도 무제한으로 적용할 수 있다.

@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;
    }
}

id : @NotNull 추가
quantity : @Max(9999) 제거

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

수정을 실행하면 정상 동작하는 것을 확인할 수 있다.
하지만 등록에서 문제가 발생한다.
등록시에는 id에 값도 없고, quantity 수량 제한 최대 값인 9999도 적용되지 않는 문제가 발생한다.

등록시 화면이 넘어가지 않으면서 다음과 같은 오류를 볼 수 있다.
'id': rejected value [null];
왜냐하면 등록시에는 id에 값이 없기 때문에 @NotNull id를 적용한 것 때문에 검증에 실패하고 다시 폼 화면으로 넘어온다.
결국 등록 자체도 불가능하고, 수량 제한도 걸지 못한다.

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

Bean Validation - groups

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

두 개의 방법이 있다.

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

BeanValidation groups 기능 사용

BeanValidation은 groups라는 기능을 제공한다.
예를 들어, 등록시에 검증할 기능과 수정시에 검증할 기능을 각각 그룹으로 나누어 적용할 수 있다.

groups 적용

저장용 groups 생성

public interface SaveCheck {
}

수정용 groups 생성

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;

    @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, UpdateCheck Groups 적용

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

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

@Valid에는 groups를 적용할 수 있는 기능이 없다. 따라서 groups를 사용하려면 @Validated를 사용해야 한다.

  • addItem()를 복사해서 addItemV2()생성, SaveCheck.class 적용
  • edit()를 복사해서 editV2()생성, UpdateCheck.class 적용

실행하면 정상적으로 작동하는 것을 확인할 수 있다.

정리

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

출처 : 인프런 김영한 지식공유자님 강의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
728x90
Comments