검증2 / Bean Validation

강한친구·2022년 6월 20일
0

Spring

목록 보기
20/27

Bean Validation이란?

기존의 검증방법은 사실 코드를 일일히 작성해야하는 불편함 이 있었다.

하지만 대부분의 검증은 해당 값이 비어있다거나, 타입이 다르다거나, 범위설정의 문제이다. 따라서 이를 편하게 해결해주기 위해 어노테이션으로 검증을 처리하는 bean validation 기술이 있다.

스프링의 bean validation 인식

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

이런 코드를 gradle에 등록해줘야 글로벌 validator가 등록이 되고 빈 검증기가 정상작동한다

    private Long id;

    @NotBlank(message="공백X")
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 10000)
    private Integer price;

    @NotNull
    @Max(9999)
    private Integer quantity;

이렇게 어노테이션으로 값을 주면 알아서 처리해준다

오류메시지 또한 errors.properties에 등록해서 지정해줄 수 있다.

NotBlank={0} 공백X 
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}

이런식으로 해두면

오류메시지가 잘 출력된다.

오브젝트 오류

@ScriptAssert()

스크립트 어썰트 어노테이션을 이용하면 object오류 처리를 해줄 수 있지만, 실제로 이는 복잡하거나 사용이 어려워서 잘 쓰 이지 않는다.

따라서 오브젝트 오류 부분은 그냥 controller에 넣거나 method로 뽑아서 처리하는게 낫다.

edit validation 처리

bean으로 item 객체에 이미 오류처리를 해놨기때문에, object error처리만 해주면 수정 역시 잘 동작한다

public String edit(@PathVariable Long itemId, @Validated @ModelAttribute Item 
item, BindingResult bindingResult)

하지만 bean validation에도 한계가 존재한다.

한계

만약 edit에서는 quantity max가 없어도 되지만 add에서는 필요하다고 하면 bean으로는 이를 처리할 수가 없게 된다.

따라서 이를 해결하기 위해서

  1. Bean Groups 사용
  2. Form 분할

Bean Groups

저장용 그룹 인터페이스 SavedCheck와 업데이트용 그룹 인터페이스 UpdateCheck를 만든다. 그 후, 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 = 10000, groups = {SaveCheck.class, UpdateCheck.class})
    private Integer price;

    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    @Max(9999, groups = {SaveCheck.class})
    private Integer quantity;

이런식으로 group을 지정해준다.

적용

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

이런식으로 add의 validate뒤에 SaveCheck를 써주면, 해당 그룹에 지정된 값들만 적용되게 된다.

반대로 edit에도 UpdateCheck를 써주면 된다.

이런식으로 두 값이 다르게 나오게 된다.

단점

일단 이런 방식은 복잡하기도 하지만, 실제로 등록과 수정이 다른 폼을 사용하는 경우가 굉장히 많다. 따라서 실전에서는 이 방식보다는 form 객체 분리 전송을 많이 사용하는 편이다.

Form 객체 분리 전송

실무에서는 groups를 잘 사용하지 않는데, 이는 등록시 폼에서 전달하는 데이터가 item 도메인과 잘 맞지 않기 때문이다.

실제로는 등록때 다른 각종 데이터를 받으며, 수정할 때도 다른 데이터를 받는다.

따라서 이를 분리해서 별도의 객체로 만들고 별도로 전달받는것이 좋다.

두 객체

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min=1000, max=10000)
    private Integer price;

    @NotNull
    @Max(value=9999)
    private Integer quantity;
    @NotNull
    private long id;

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min=1000, max=10000)
    private Integer price;

    private Integer quantity;

이런식으로 등록용 폼, 수정용 폼을 만들고 controller에서 고쳐주면 된다.

@PostMapping("/add")
    public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    ... 중략
    
    //item으로 변환할것!
    Item item = new Item();
    item.setItemName(form.getItemName());
    item.setPrice(form.getPrice());
    item.setQuantity(form.getQuantity());
    ... 중략
}    

주의할 점은 다음과 같다.

  1. ModelAttribute이름을 고려할 것
  2. form에는 부족한 정보가 있으니 item도메인으로 변환해서 사용할 것
    이렇게 하지 않으면 규칙에 의해 itemForm...이름의 모델이 생성되고 이는 thymeleaf에서 오류로 나타난다.

이와 같은 폼 분리 방식이 실무에서 가장 많이 사용되는 방식이다.

HTTP 컨버터 / Bean Validation

RequestBody에서도 validation은 작동할 수 있다.

성공 요청: 성공

성공하는 경우 기존의 성공과 별 다를것 없다.

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

이런 경우, json이 form자체로 변환되는것조차 실패하기에 controller가 작동하지 않는다.

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

검증 오류를 json 형태로 전달해준다. 필요한 데이터만 선정해서 보내줄 수 있다.

정리

  • @ModelAttribute 는 필드 단위로 정교하게 바인딩이 적용된다. 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator를 사용한 검증도 적용할 수 있다.

  • @RequestBody 는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다. 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.

따라서 보통 RequestBody에서 발생하는 예외는 따로 예외처리를 해준다.

0개의 댓글