검증 처리 코드
1. 검증 오류를 보관할 HashMap을 생성한다.
2. 상품 이름이 빠지거나, 가격의 범위를 넘어서거나, 수량 범위를 넘어설 때 검증할 로직을 추가한다.
3. 특정 필드가 아닌 복합 적인 검증 로직도 추가한다.
4. 만약 2번 3번에서 검증에 실패하면 errors에 에러 이유가 들어가 있을 것이고, 에러가 있으면 validation/v1/addForm으로 다시 이동한다.
@PostMapping("/add")
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model) {
// 검증 오류 보관
Map<String, String> errors = new HashMap<>();
// 검증 로직
if (!StringUtils.hasText(item.getItemName())) {
errors.put("itemName", "상품 이름은 필수입니다.");
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
errors.put("price", "가격은 1,000 ~ 1,000,000 까지 허용합니다.");
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
errors.put("quantity", "수량은 최대 9,999 까지 허용합니다.");
}
// 특정 필드가 아닌 복합 룰 검증
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
errors.put("globalError", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice);
}
}
// 검증에 실패하면 다시 입력 폼으로
if (!errors.isEmpty()) { // hasError로 바꾸는게 좋음. 부정의 부정이라
model.addAttribute("errors", errors);
return "validation/v1/addForm";
}
// 성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v1/items/{itemId}";
}
근데 여기서 궁금한 것이 생긴다. 페이지를 다시 로딩 했는데 어떻게 폼에 값이 그대로 남아 있는가 ?
1. @ModelAttribute 속성 때문에 model.add("item", item)을 자동으로 해주며, 이를 view로 전송한다.
2. 이를 HTML파일에서 읽어들일 수 있기 때문에 폼에 값이 그대로 남게 된다.
리팩토링
@BindingResult
1. BindingResult는 Model에 자동으로 포함된다.
2. @ModelAttribute에 바인딩 시 타입 오류가 발생하면 BindingResult에 오류 정보를 담아서 컨트롤러를 호출한다.
Cf ) BindingResult가 없으면 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.
1. BindingResult를 선언한다. 이 때 위치는 @ModelAttribute가 붙은 파라미터 바로 옆에 선언해야 한다. -> 에러가 생기면 @ModelAttribute가 붙은 파라미터에 바인딩 된 결과가 담긴다.
@PostMapping("/add") // item에 바인딩 된 결과가 담김
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes,
Model model) {
2. 필드에 오류가 있으면 FieldError 객체를 생성해서 bindingResult에 담아두면 된다.
// 검증 로직
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수 입니다."));
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.addError(new FieldError("item", "price", "가격은 1,000 ~ 1,000,000 까지 허용합니다."));
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
bindingResult.addError(new FieldError("item", "price", "수량은 최대 9,999 까지 허용합니다."));
}
3. 특정 필드를 넘어서는 복합 오류의 경우 ObjectError를 생성해서 bindingResult에 담아야 한다.
// 특정 필드가 아닌 복합 룰 검증
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
// 필드가 없음. 글로벌 오류임 따라서 ObjectError이용
bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다."));
}
}
4. 검증에 실패하면 다시 입력 폼으로 이동하게 한다.
// 검증에 실패하면 다시 입력 폼으로
if (bindingResult.hasErrors()) { // 바인딩리서트는 모델에 안담아도 스프링이 알아서 해 줌.
return "validation/v2/addForm";
}