
BindingResult1스프링이 제공하는 검증 오류 처리 방법인 BindingResult에 대해 알아보자!
ValidationItemControllerV2 - addItemV1BindingResult bindingResult 파라미터의 위치public String addItemV1(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) { }
BindingResult bindingResult 파라미터의 위치는 @ModelAttribute Item item 다음에 와야 한다!
FieldErrorif (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError("item", "itemName", "상품 이름은 필수입니다."));
}
FieldError 객체를 생성해서 bindingResult에 담아두면 된다.public FieldError(String objectName, String field, String defaultMessage) {}objectName: @ModelAttribute 이름field: 오류가 발생한 필드 이름defaultMessage: 오류 기본 메시지ObjectErrorbindingResult.addError(new ObjectError("item", "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));
ObjectError 객체를 생성해서 bindingResult에 담아두면 된다.public ObjectError(String objectName, String defaultMessage) {}objectName: @ModelAttribute의 이름defaultMessage: 오류 기본 메시지타임리프는 스프링의 BindingResult를 활용해서 편리하게 검증 오류를 표현하는 기능을 제공한다.
#fields: #fields로 BindingResult가 제공하는 검증 오류에 접근할 수 있다.th:errors: 해당 필드에 오류가 있는 경우에 태그를 출력한다. th:if의 편의 버전!th:errorclass: th:field에서 지정한 필드에 오류가 있으면 class 정보를 추가한다.


BindingResult2BindingResult가 있으면 @ModelAttribute에 데이터 바인딩 시 오류가 발생해도 컨트롤러가 호출된다!
예를 들어, 바인딩 시 타입 오류가 발생하면?
BindingResult❌ → 400 오류 발생 → 컨트롤러 호출 ❌, 오류 페이지로 이동한다!BindingResult⭕→ 오류 정보(FieldError)를 BindingResult에 담아서 컨트롤러를 정상 호출한다!BindingResult에 검증 오류를 적용하는 3가지 방법@ModelAttribute의 객체에 타입 오류 등으로 바인딩이 실패하는 경우, 스프링이 FieldError 생성해서 BindingResult에 넣어준다.Validator 사용BindingResult와 Errors
org.springframework.validation.Errors
org.springframework.validation.BindingResult
BindingResult는 인터페이스이고, Errors 인터페이스를 상속받고 있다.BeanPropertyBindingResult인데,BindingResult 대신에 Errors를 사용해도 된다!Errors 인터페이스는 단순한 오류 저장과 조회 기능을 제공하며, BindingResult는 여기에 더해서 추가적인 기능까지 제공한다. addError()도 BindingResult만 제공하므로 여기서는 BindingResult를 사용해야 한다.BindingResult를 많이 사용한다.FieldError, ObjectError📌 목표
- 사용자 입력 오류 메시지가 화면에 표시되도록 하자.
FieldError,ObjectError에 대해서 더 자세히 알아보자.
ValidationItemControllerV2 - addItemV2FieldError 생성자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: 기본 오류 메시지new FieldError("item", "price", item.getPrice(), false, null, null, "가격은 1,000 ~ 1,000,000 까지 허용합니다.")
FieldError: 오류 발생시 사용자 입력 값을 저장하는 기능을 제공한다. rejectedValue: 오류 발생시 사용자 입력 값을 저장하는 필드bindingFailure: 타입 오류 같은 바인딩이 실패했는지 여부 저장.th:field="*{price}"
타임리프의 th:field 동작은 다음과 같다.
FieldError에서 보관한 값을 사용해서 값을 출력한다.타입 오류로 바인딩에 실패하면 스프링은 FieldError를 생성하면서 사용자가 입력한 값을 넣어둔다.
그리고 해당 오류를 BindingResult에 담아서 컨트롤러를 호출한다.
→ 타입 오류 같은 바인딩 실패시에도 사용자의 오류 메시지를 정상 출력할 수 있다.

이제 오류가 발생해도 입력 값이 유지된다!
FieldError, ObjectError의 생성자는 errorCode, arguments를 제공한다.
이것은 오류 발생시 오류 코드로 메시지를 찾기 위해 사용된다!
error 메시지 파일 생성오류 메시지를 저장하기 위해 errors.properties 파일을 만들자!
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
이렇게 파일을 따로 빼두면, 하드코딩을 할 필요가 없어서 유지보수가 쉬워진다!
ValidationItemControllerV2 - addItemV3new FieldError("item", "price", item.getPrice(), false, new String[]{"range.item.price"}, new Object[]{1000, 1000000}
codesrequired.item.itemName를 사용해서 메시지 코드를 지정한다.arguments: Object[]{1000,1000000}를 사용해서 코드의 {0}, {1}로 치환할 값을 전달한다.
바로 위에서 배운 FieldError, ObjectError 보다 더 편리한 방법에 대해 알아보자!
rejectValue(), reject()BindingResult가 제공하는 rejectValue(), reject()를 사용하면
FieldError, ObjectError를 직접 생성하지 않고, 깔끔하게 검증 오류를 다룰 수 있다!
ValidationItemControllerV2 - addItemV4rejectValue()void rejectValue(@Nullable String field, String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
field: 오류 필드명errorCode: 오류 코드 (messageResolver를 위한 오류 코드)errorArgs: 오류 메시지에서 {0}을 치환하기 위한 값defaultMessage: 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지FieldError()를 직접 사용했을 때는,
range.item.price와 같이 오류 코드를 모두 입력해야 했다.
rejectValue()를 사용했을 때는,
range처럼 간단하게 입력해도 오류 메시지를 잘 찾아서 출력해준다!

