티스토리 뷰
본 글은 다크모드에 최적화되어 있습니다.
스프링에서는 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도 사용할 수 없다.