스프링이 제공하는 검증 오류를 보관하는 객체이다. 검증 오류가 발생하면 여기에 보관하면 된다.
BindingResult
가 있으면 @ModelAttribute
에 데이터 바인딩 시 오류가 발생해도 컨트롤러가 호출 된다.
ex) @ModelAttribute에 바인딩 시 타입 오류가 발생하면?
BindingResult
가 없으면 => 400 오류가 발생하면서 컨트롤러가 호출되지 않고, 오류 페이지로 이동한다.
BindingResult
가 있으면 => 오류 정보 (FieldError
)를 BindingResult
에 담아서 컨트롤러를 정상 호출한다.
@ModelAttribute
의 객체에 타입 오류 등으로 바인딩이 실패하는 경우 스프링이 FieldError
생성해서 BindingResult
에 넣어 준다.validator
사용숫자가 입력되어야 할 곳에 문자를 입력해서 타입을 다르게 해서 BindingResult
를 호출하고 bindingResult
의 값을 확인해보자.
📍주의:
BidingResult
는 검증할 대상 바로 다음에 와야한다. 순서가 중요하다. 예를 들어서 @ModelAttribute Item item
, 바로 다음에 BindingResult
가 와야 한다.
BindingResult
는 Model에 자동으로 포함된다.
org.springframework.validation.Errors
org.springframework.validation.BindingResult
BindingResult
는 인터페이스이고, Errors
인터페이스를 상속 받고 있다.
실제 넘어오는 구현체는 BeanPropertyBindingResult
라는 것인데, 둘다 구현하고 있으므로 BidingResult
대신에 Errors
를 사용해도 된다. Errors
인터페이스ㄹ는 단순한 오류 저장과 조회 기능을 제공한다. BindingResult
는 여기에 더해서 추가적인 기능들을 제공한다. addError()
도 BindingResult
가 제공하므로 여기서는 BidingResult
를 사용
주로 관례상 BidingResult
를 많이 사용한다.
BindingResult , FieldError , ObjectError 를 사용해서 오류 메시지를 처리하는 방법을 알아보았다. 그런데 오류가 발생하는 경우 고객이 입력한 내용이 모두 사라진다. 이 문제를 해결해보자.
사용자 입력 오류 메시지가 화면에 남도록 하자.
예) 가격을 1000원 미만으로 설정시 입력한 값이 남아있어야 한다.
FieldError
, ObjectError
에 대해서 더 자세히 알아보자.
@PostMapping("/add")
public String addItemV2(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes){
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", item.getItemName(), false, null, null, "상품 이름은 필수입니다."));
}
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.addError(new FieldError("item", "price", item.getPrice(),false, null, null, "가격은 1,000 ~ 1,000,000 까지 허용합니다."));
}
if (item.getQuantity() == null || item.getQuantity() > 10000) { bindingResult.addError(new FieldError("item", "quantity", item.getQuantity(),false, null, null, "수량은 최대 9,999 까지 허용합니다."));
}
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.addError(new ObjectError("item", null, null, "가격 * 수량의 합은 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}";
}
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)
파라미터 목록
objectName
: 오류가 발생한 객체 이름
field
: 오류 필드
rejectedValue
: 사용자가 입력한 값 (거절된 값)
bindingFailure
: 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
codes
: 메세지 코드
arguments
: 메세지에서 사용하는 인자
defaultMessage
: 기본 오류 메세지
ObjectError
도 유사하게 두 가지 생성자를 제공한다.
new FieldError("item", "price", item.getPrice(), false, null, null, "가격은 1,000 ~ 1,000,000 까지 허용합니다.")
사용자의 입력 데이터가 컨트롤러의 @ModelAttribute
에 바인딩되는 시점에 오류가 발생하면 모델 객체에 사용자 입력 값을 유지하기 어렵다. 예를 들어서 가격에 숫자가 아닌 문자가 입력된다면 가격은 Integer
타입이므로 문자를 보관할 수 있는 방법이 없다. 그래서 오류가 발생한 경우 사용자 입력 값을 보관하는 별도의 방법이 필요하다. 그리고 이렇게 보관한 사용자 입력 값을 검증 오류 발생시 화면에 다시 출력하면 된다.
FieldError
는 오류 발생시 사용자 입력 값을 저장하는 기능을 제공한다.
여기서 rejectedValue
가 바로 오류 발생시 사용자 입력 값을 저장하는 필드다.
bindingFailure
는 타입 오류 같은 바인딩이 실패했는지 여부를 적어주면 된다. 여기서는 바인딩이 실패한 것은 아니기 때문에 false
를 사용한다.
th:field="*{price}"
타임리프의 th:field
는 매우 똑똑하게 동작하는데, 정상 상황에는 모델 객체의 값을 사용하지만, 오류가
발생하면 FieldError
에서 보관한 값을 사용해서 값을 출력한다.
타입 오류로 바인딩에 실패하면 스프링은 FieldError
를 생성하면서 사용자가 입력한 값을 넣어둔다. 그리고 해당 오류를 BindingResult
에 담아서 컨트롤러를 호출한다. 따라서 타입 오류 같은 바인딩 실패시에도 사용자의 오류 메시지를 정상 출력할 수 있다.