ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] 입력(전송) 값 검증(Validation)
    legacy/Spring 2023. 5. 23. 12:10

    본 글은 다크모드에 최적화되어 있습니다.

     

     

    스프링에서는 form을 통해 전달받은 값에 대하여 값을 검증하는 기능과 API JSON 요청을 통해 전달받은 값에 대하여 검증을 수행할 수 있다.

     

    설명에 들어가기 앞서 결론은 다음과 같다.

    • form으로 전송한 값을 @ModelAttribute를 통해 받으면 특정 필드가 바인딩에 실패해도 검증을 수행한다.
    • API JSON 통신으로 전송한 값을 @RequestBody를 통해 받으면 필드 바인딩에 실패하면 검증 자체를 수행하지 않는다.

    본 글은 @ModelAttribute와 @RequestBody 애너테이션 설명 글이 아니므로 간단하게만 설명하낟.

    @ModelAttirbute : Http 요청으로 전송한 값을 Controller에서 필드로 받을 필요 없이 객체로 바로 바인딩 한다.

    @RequestBody : JSON API로 요청한 값을 객체로 바인딩 한다.

     


    @ModelAttribute

    @ModelAttribute 애너테이션 설명에 사용되는 ItemSaveForm 클래스와 컨트롤러는 다음과 같다.

    컨트롤러가 호출되면 saveItem 객체를 log로 남기고 바인딩 실패 유무에 따라 클라이언트에 보여 줄 view가 달라진다.

    ItemSaveForm의 필드에 사용된 Bean Validation에 대해서는 설명하지 않는다. (궁금하면 구글링하길 바란다)

    @Data
    public class ItemSaveForm {
    
        @NotBlank
        private String itemName;
    
        @NotNull
        @Range(min = 10000, max = 1000000)
        private Integer price;
    
        @NotNull
        @Max(9999)
        private Integer quantity;
    }
    
    @Slf4j
    @Controller
    @RequestMapping("/validation/v4/items")
    @RequiredArgsConstructor
    public class ValidationItemControllerV4 {
    
        private final ItemRepository itemRepository;
    
        @PostMapping("/add")
        public String addItem(@Validated @ModelAttribute("item") ItemSaveForm saveItem, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
    
            log.info("item={}", saveItem);
    
            if(saveItem.getPrice() != null && saveItem.getQuantity() != null) {
                int result = saveItem.getQuantity() * saveItem.getPrice();
                if(result < 10000)
                    bindingResult.reject("totalPrice", null, null);
            }
    
            if(bindingResult.hasErrors()){
                log.info("검증 실패");
                return "/validation/v4/addForm";
            }
    
            log.info("검증 성공");
            Item item = new Item(saveItem.getItemName(), saveItem.getPrice(), saveItem.getQuantity());
            Item savedItem = itemRepository.save(item);
            redirectAttributes.addAttribute("itemId", savedItem.getId());
            redirectAttributes.addAttribute("status", true);
            return "redirect:/validation/v4/items/{itemId}";
        }
    }

     

    • 검증 성공

    특별히 설명할 부분이 없으므로 다음으로 넘어가자

     

    • 검증 실패1

    ItemSaveForm의 price 필드의 최소 가격이 10,000으로 설정되어 있다. 따라서 1,000을 저장하려고 하면 검증 실패가 된다.

     

    • 검증 실패2

    ItemSaveForm의 price 필드를 보면 타입이 Integer이다. 따라서 문자 A를 저장하려고 하면 검증에 실패한다.

    올바르지 않는 타입의 값을 넣어도 log에 item 객체가 찍힌다.

     

    @ModelAttribute의 경우에는 올바르지 않는 입력값을 넣어 전송해도 Controller가 정상적으로 호출된다!!

     


    @RequestBody

    @RequestBody 애너테이션을 설명하기 위해 사용된 ItemSaveForm 클래스와 Controller는 다음과 같다.

    컨트롤러가 호출되면 클라이언트에 view를 보여주는 것이 아니라 값을 전달한다.

    @Data
    public class ItemSaveForm {
    
        @NotBlank
        private String itemName;
    
        @NotNull
        @Range(min = 10000, max = 1000000)
        private Integer price;
    
        @NotNull
        @Max(9999)
        private Integer quantity;
    }
    @Slf4j
    @RestController
    @RequestMapping("/validation/api/items")
    public class ValidationApiController {
    
        @PostMapping("/add")
        public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult) {
            log.info("itemSave={}", form);
    
            if(bindingResult.hasErrors()) {
                log.info("검증 실패");
                return bindingResult.getAllErrors();
            }
    
            log.info("검증 성공");
            return form;
        }
    }

     

    • 검증 성공

    딱히 설명할 것이 없으므로 다음으로 넘어간다.

     

    • 검증 실패1

    ItemSaveForm의 price 필드의 최소 가격이 10,000으로 설정되어 있다. 따라서 1,000을 저장하려고 하면 검증 실패가 된다.

     

    • 검증 실패2

    검증 실패1과 출력 결과가 다르다. log 자체가 찍히지도 않았다.  price 필드는 Integer 타입이기 때문에 문자를 저장할 수 없다. 이 경우는 ItemSaveForm 객체 자체를 생성하지 못한 경우이다. 그렇기에 Controller 자체가 호출되지 못하고 예외가 발생한 경우이다.

     

    @RequestBody는 올바르지 않은 값을 전송하면 객체를 생성하지 못하기 때문에 Controller 자체가 호출되지 않는다!!


     

    이렇게 @ModelAttribute와 @RequestBody에서 검증 처리 방법을 알아보았다.

    Http 요청 파라미터를 처리하는 @ModelAttribute는 각각의 필드 단위로 세밀하게 처리한다.

    JSON 데이터를 처리하는 @RequestBody는 HttpMessageConverter를 이용하는데, 각각의 필드 단위로 처리하는 것이 아니라 전체 객체 단위로 처리한다.

     


    결론

    @ModelAttribute는 모든 필드에 세밀하게 적용되므로, 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상적으로 바인딩 된다. 따라서 Validator를 통한 검증도 가능하다.

    @RequestBody는 하나의 필드라도 바인딩에 실패하면 JSON 데이터를 객체로 변경하지 못하므로 객체 생성에 실패한다. 따라서 Controller도 호출되지 않고 Validator도 사용할 수 없다.


     

Designed by Tistory.