[Spring MVC] 검증 - Field/ObjectError
목표
- 사용자 입력 오류 메시지가 화면에 남아야 한다.
FieldError
,ObjectError
에 대한 더 자세한 내용.
addItemV2
@PostMapping("/add")
public String addItemV2(@ModelAttribute Item item, BindingResult bindingResult,
RedirectAttributes 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.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.addError(new ObjectError("item", null, null, "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));
}
}
}
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
: 기본 오류 메시지
타임리프의 사용자 입력 값 유지
th:field="*{price}"
타임리프의 th:field
는 정상 상황에서는 모델 객체의 값을 사용하지만, 오류 발생시 FieldError
에서 보관한 값을 사용해서 값을 출력한다.
스프링의 바인딩 오류 처리
타입 오류로 바인딩 실패시 스프링은 FieldError
를 생성하여 사용자가 입력한 값을 넣어두고, 해당 오류를 BindingResult
에 담아서 컨트롤러를 호출한다. 따라서 타입 오류 같은 바인딩 실패시에도 오류 메시지를 정상 출력할 수 있다.
bindingResult.addError(new FieldError("item", "price", "qqq", ...))
addItemV3
FieldError
, ObjectError
의 생성자는 errorCode
, arguments
를 제공하는데, 이는 오류 발생시 오류 코드로 메시지를 찾기 위함이다.
스프링 부트 메시지 설정 추가
application.properties
spring.messages.basename=messages, errors
생략하면 messages.properties
를 기본으로 인식한다.
errors.properties
errors.properties
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
Controller
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.addError(new FieldError(
"item", "itemName", item.getItemName(), false, new String[]{"required.item.itemName"}, null, null));
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.addError(new FieldError(
"item", "price", item.getPrice(), false, new String[]{"range.item.price"}, new Object[]{1000, 1000000}, null));
}
// 수량 검증 생략...
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.addError(new ObjectError(
"item", new String[]{"totalPriceMin"}, new Object[]{10000, resultPrice}, null));
}
}
codes
:required.item.itemName
를 사용해서 메시지 코드 지정. 메시지 코드는 배열로 여러 값을 전달할 수 있어, 순서대로 매칭해서 처음 매칭되는 메시지가 사용된다.arguments
:Object[]{1000, 1000000}
를 사용해서 코드의{0}
,{1}
로 치환할 값을 전달한다.
실행하면 MessageSource를 찾아서 메시지를 조회한다.
addItemV4
FieldError
,ObjectError
는 사용하기 번거로운 감이 있다.- 그래서, 오류 코드도 좀 더 자동화 시키기로 했다.
BindingResult
는 검증해야 할 객체(target
)바로 다음에 온다.
따라서, BindingResult
는 이미 검증해야 할 객체인 target
을 알고있다.
rejectValue(), reject()
BindingResult
가 제공하는 rejectValue()
, reject()
를 사용하면 FieldError
, ObjectError
를 직접 생성하지 않고 오류를 다룰 수 있다.
Controller
if (!StringUtils.hasText(item.getItemName())) {
bindingResult.rejectValue("itemName", "required");
}
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000}, null);
}
// 수량 검증 생략...
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
rejectValue()
void rejectValue(@Nullable String field, String errorCode,
@Nullable Object[] errorArgs, @Nullable String defaultMessage);
BindingResult
는 target
을 이미 알고 있기 때문에 target(item)
에 대한 정보는 없어도 된다.
reject()
void reject(String errorCode, @Nullable Object[] errorArgs, @Nullable String defaultMessage);
축약된 오류 코드
rejectValue()
를 사용하니 오류 코드를 range
로 간단하게 입력했다. 이 부분은 MessageCodesResolver를 이해해야 한다.
<출처 : 인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술(김영한)>
댓글남기기