728x90
AddItemV1
@PostMapping("/add")
public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
//검증 로직
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", "quantity", "수량은 최대 9,999 까지 허용합니다"));
}
//특정 필드가 아닌 복합 룰 검증
if(item.getPrice() != null && item.getQuantity() !=null ){
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice < 10000){
bindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));
}
}
//검증에 실패하면 다시 입력 폼으로
if(bindingResult.hasErrors()){
log.info("bindingResult = {}", bindingResult);
//model.addAttribute("errors", bindingResult); //자동으로 뷰에 넘어가기 때문에 모델에 담을 필요 x
return "validation/v2/addForm";
}
//성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v2/items/{itemId}";
}
- BindingResult 같은 경우 반드시 @ModelAttribute 가 붙어있는 item 뒤에 파라미터로 전달되어야 한다(끝이나, 다른 곳에 전달되면 안 된다)
- 더 이상 Map으로 담아 주었던 일의 BindResult를 통해 담아주면 된다
- 필드에 오류가 있으면 FieldError 객체를 생성해서 bindingResult에 담아두면 된다.
- 글로벌 오류가 있으면 objectError 객체를 생성해서 bindingResult 에 담아두면 된다.
public FieldError(String objectName, String field, String defaultMessage) {}
public ObjectError(String objectName, String defaultMessage) {}
AddForm.HTML
<div class="container">
<form action="item.html" th:action th:object="${item}" method="post">
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>
</div>
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}"
th:errorclass="field-error"
class="form-control" placeholder="이름을 입력하세요">
<div class="field-error" th:errors="*{itemName}">
상품명 오류
</div>
</div>
<div>
<label for="price"
th:text="#{label.item.price}">가격</label>
<input type="text" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요" th:errorclass="field-error">
<div class="field-error" th:errors="*{price}">
가격 오류
</div>
</div>
<div>
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
<input type="text" id="quantity" th:field="*{quantity}"
th:errorclass="field-error"
class="form-control" placeholder="수량을 입력하세요">
<div class="field-error" th:errors="*{quantity}">
수량 오류
</div>
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.save}">상품 등록</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/validation/v2/items}'|"
type="button" th:text="#{button.cancel}">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
- #fields : #fields로 BindingResult 가 제공하는 검증 오류에 접근할 수 있다.
- th:errors : 해당 필드에 오류가 있는 경우에 태그를 출력한다. th:if의 편의 버전이다.
- th:errorclass : th:field에서 지정한 필드에 오류가 있으면 class 정보를 추가한다
BindingResult 자세히 살펴보기
- 스프링이 제공하는 검증 오류를 보관하는 객체이다. 검증 오류가 발생하면 여기에 보관하면 된다.
- BindingResult 가 있으면 @ModelAttribute에 데이터 바인딩 시 오류가 발생해도 컨트롤러가 호출된다!
- Ex) @ModelAttribute에 바인딩 시 타입 오류가 발생하면?
- BindingResult 가 없으면 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.
- BindingResult 가 있으면 오류 정보( FieldError )를 BindingResult에 담아서 컨트롤러를 정상 호출한다.
- BindingResult에 검증 오류를 적용하는 3가지 방법
- @ModelAttribute의 객체에 타입 오류 등으로 바인딩이 실패하는 경우 스프링이 FieldError 생성해서 BindingResult에 넣어준다.
- 개발자가 직접 넣어준다.
- Validator 사용 이것은 뒤에서 설명
- 주의
- BindingResult는 검증할 대상 바로 다음에 와야한다. 순서가 중요하다. 예를 들어서 @ModelAttribute Item item , 바로 다음에 BindingResult 가 와야 한다.
- BindingResult 는 Model에 자동으로 포함된다
- BindingResult와 Errors
- BindingResult 는 인터페이스이고, Errors 인터페이스를 상속받고 있다.
- 실제 넘어오는 구현체는 BeanPropertyBindingResult라는 것인데, 둘 다 구현하고 있으므로 BindingResult 대신에 Errors를 사용해도 된다.
- Errors 인터페이스는 단순한 오류 저장과 조회 기능을 제공한다. BindingResult는 여기에 더해서 추가적인 기능들을 제공한다.
- addError() 도 BindingResult 가 제공하므로 여기서는 BindingResult를 사용하자. 주로 관례상 BindingResult 를 많이 사용한다.
728x90
'스프링 MVC 2편(백엔드 웹 개발 활용 기술)' 카테고리의 다른 글
| Ch04. 검증(Validation) - 오류 코드와 메시지 처리 (0) | 2022.03.15 |
|---|---|
| Ch04. 검증(Validation) - FliedError, ObjectError (0) | 2022.03.15 |
| Ch04. 검증(Validation) - 검증 직접 처리 (0) | 2022.03.14 |
| Ch04. 검증(Validation) - 검증 요구사항 (0) | 2022.03.14 |
| Ch03. 메시지, 국제화 - 웹 애플리케이션에 국제화 적용하기 (0) | 2022.03.13 |