
고객이 잘못된 값을 입력하면 서버 검증 로직이 실패하면서 고객에게 다시 상품 등록 폼을 보이고, 잘못 입력한 부분을 친절하게 알려주어야 한다.
컨트롤러에서 오류를 검증하고 렌더링 시 해당되는 에러 메시지를 띄우도록 한다.
BindingResult
BindingResult 가 있으면 오류 정보( FieldError )를 BindingResult 에 담아서 컨트롤러를 정상 호출한다.
@PostMapping("/add") //BindingResult는 ModelAttribute 다음에 위치해야 한다.
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.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";
}
//성공 로직
}
bindingResult를 이용하여 오류 검증.
<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>
#fields : #fields 로 BindingResult 가 제공하는 검증 오류에 접근할 수 있다.
th:errors : 해당 필드에 오류가 있는 경우에 태그를 출력한다. th:if 의 편의 버전이다.
th:errorclass : th:field 에서 지정한 필드에 오류가 있으면 class 정보를 추가한다.
FieldError 생성자
파라미터 목록
objectName : 오류가 발생한 객체 이름 field : 오류 필드 rejectedValue : 사용자가 입력한 값(거절된 값) 보관 bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값 codes : 메시지 코드 arguments : 메시지에서 사용하는 인자 defaultMessage : 기본 오류 메시지
FieldError 는 두 가지 생성자를 제공한다.
public FieldError(String objectName, String field, String defaultMessage);
public FieldError(String objectName, String field, @Nullable Object
rejectedValue, boolean bindingFailure, @Nullable String[] codes,
@Nullable Object[] arguments, @Nullable String defaultMessage);
오류 코드와 메시지 처리
MessageCodesResolver
메시지를 더 범용성 있게 사용하도록 도와준다.
#Level1
totalPriceMin.item=상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
#Level2 - 생략
totalPriceMin=전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}
범용성에 따라 메시지를 나누어 놓고 bindingResult.reject("totalPriceMin",new Object[]{10000,resultPrice},null); 를 호출하여 메시지들을 문자열 배열로 받는다.
@InitBinder
public void init(WebDataBinder dataBinder) {
log.info("init binder {}", dataBinder);
dataBinder.addValidators(itemValidator);
}
컨트롤러가 실행될 때 검증기 초기화. 글로벌 설정은 별도로 함.
@PostMapping("/add")
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
}
: @Validated 애노테이션을 추가하여 모델 타입에 맞는 검증기 호출.