🔗 요구사항 확인
✏️ Binding Result 적용 - V1.
📍 Controller 계층
BindingResult
- Spring 에서 제공하는 validation 라이브러리
- Map 의 기능을
BindingResult
이 대신할 수 있으므로 Map 은 삭제한다.
- 검증을 통과하지 못할경우
BindingResult
에 error
를 추가해준다.
Model
로 넘겨주지 않아도 Spring 이 전달해준다.
FieldError
- 특정 필드에서 에러가 발생할경우
- Param 1 : Post 로 등록할 객체명을 적어주면 된다.
- Param 2 : 오류 코드를 적어주면 된다.
- Param 3 : 클라이언트 메시지를 적어주면 된다.
ObjectError
- 특정 필드가 아닌 복합 룰에 의해서 에러가 발생할경우
- Param 1 : Post 로 등록할 객체명을 적어주면 된다.
- Param 2 : 클라이언트 메시지를 적어주면 된다.
- ⚠️
BindingResult
의 파라미터 위치는 항상 바인딩의 대상이 되는 객체 바로 뒤에 위치해야 한다.
- 예제에서는
Item
이 바인딩의 대상이므로
@ModelAttribute Item item
바로 뒤에 위치시켜야 한다.
- 바로 뒤에 위치시키지 않아도 뒤에만 있다면 작동은 되지만,
모든 기능 (type 검증 등)이 제대로 작동되지 않는다.
@PostMapping("/add")
public String addItemV1(
@ModelAttribute Item item,
BindingResult bindingResult,
RedirectAttributes redirectAttributes
) {
if (!StringUtils.hasText(item.getItemName()))
bindingResult.addError(new FieldError(
"item",
"itemName",
"상품 이름은 필수입니다.")
);
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 100000)
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("errors = {}", bindingResult);
return "validation/v2/addForm";
}
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v2/items/{itemId}";
}
📍 web 계층 - global 에러 처리
- if 문을 사용해 글로벌 오류가 있다면 오류메시지를 렌더링하는 로직
- 글로벌 오류가 여러가지가 있을 수 있으므로 each 문을 사용한다.
#fields
BindingResult
의 Thymeleaf 변수 표현식
- 모델로 넘어온
BindingResult
를 사용할 수 있다.
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error"
th:each="err : ${#fields.globalErrors()}"
th:text="${err}"></p>
</div>
📍 Field 에러 처리
th:errorclass
BindingResult
에 필드 에러가 있을경우 실행된다.
th:field
에 정의된 변수를 기반으로 해당 필드가 어떤필드인지 판단한다.
- class append 처럼 참일경우 기존 class 속성에
th:errorclass
에 정의된 속성이 추가된다.
th:errors
BindingResult
에 필드 에러가 있을경우 에러메시지가 text 로 출력된다.
BindingResult
의 매개변수인 object name 과 field 를 기반으로 처리됨
- 상위 테그에
th:object
로 선언했다면 예제처럼 선택변수를 사용할 수 있다.
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text"
th:field="*{itemName}"
th:errorclass="field-error" class="form-control"
placeholder="이름을 입력하세요">
<div class="field-error"
th:errors="*{itemName}">
</div>
📍 나머지 코드도 변경
<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"
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"
th:field="*{price}"
th:errorclass="field-error" class="form-control"
placeholder="가격을 입력하세요">
<div class="field-error"
th:errors="*{price}">
</div>
</div>
<div>
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
<input type="text"
th:field="*{quantity}"
th:errorclass="field-error" class="form-control"
placeholder="수량을 입력하세요">
<div class="field-error"
th:errors="*{quantity}">
</div>
</div>
✏️ 문제점
- 필드 에러가 발생할경우 클라이언트가 입력한 data 가 유지되지 않는다.