# messages와 errors 두 곳 모두 찾아 쓰도록 설정한다.# 생략하면 기본값인 messages만 읽어들인다.spring.messages.basename=messages,errors
required.item.itemName=상품 이름은 필수입니다.range.item.price=가격은 {0} ~ {1} 까지 허용합니다.max.item.quantity=수량은 최대 {0} 까지 허용합니다.totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
@Slf4j@Controller@RequestMapping("/validation/v2/items")@RequiredArgsConstructorpublicclassValidationItemControllerV2 { @PostMapping("/add")publicStringaddItemV3(@ModelAttributeItem item,BindingResult bindingResult,RedirectAttributes redirectAttributes) {if (!StringUtils.hasText(item.getItemName())) {bindingResult.addError(newFieldError("itemName","itemName",item.getItemName(),false,newString[]{"required.item.itemName"},null,null)); }if (item.getPrice() ==null||item.getPrice() <1000||item.getPrice() >1_000_000) {bindingResult.addError(newFieldError("item","price",item.getPrice(),false,newString[]{"range.item.price"},newObject[]{100,1_000_000},null)); }if (item.getQuantity() ==null||item.getQuantity() >=9999) {bindingResult.addError(newFieldError("item","quantity",item.getQuantity(),false,newString[]{"max.item.quantity"},newObject[]{9_999},null)); }if (item.getPrice() !=null&&item.getQuantity() !=null) {int resultPrice =item.getPrice() *item.getQuantity();if (resultPrice <10000) {// objectError는 기존 값이 있는 게 아니므로 fieldError 같은 생성자는 없다.bindingResult.addError(newObjectError("item",newString[]{"totalPriceMin"},newObject[]{10_000, resultPrice},null)); } }...return"redirect:/validation/v2/items/{itemId}"; }}
기존 application.properties에 설정을 추가한다. 컨트롤러에는 이제 defaultMessage 파라미터를 넘기는 대신 code와 argument 파라미터에 데이터를 넘기면 errors.properties와 매핑된다.
codes
메시지 코드를 지정한다.
배열이 들어가는 이유는 해당 키로 errors.properties에서 값을 찾지 못했을 때 다음 인덱스의 값으로 찾을 수 있기 때문이다.
## ObjectError# Level 1totalPriceMin.item=상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}# Level 2 - 생략totalPriceMin=전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}## FieldError# Level 1required.item.itemName=상품 이름은 필수입니다.range.item.price=가격은 {0} ~ {1} 까지 허용합니다.max.item.quantity=수량은 최대 {0} 까지 허용합니다.# Level 2 - 생략# Level 3required.java.lang.String=필수 문자입니다.required.java.lang.Integer=필수 숫자입니다.min.java.lang.String={0} 이상의 문자를 입력해주세요.min.java.lang.Integer={0} 이상의 숫자를 입력해주세요.range.java.lang.String={0} ~ {1} 까지의 문자를 입력해주세요.range.java.lang.Integer={0} ~ {1} 까지의 숫자를 입력해주세요.max.java.lang.String={0} 까지의 문자를 허용합니다.max.java.lang.Integer={0} 까지의 숫자를 허용합니다.# Level 4required=필수 값 입니다.min={0} 이상이어야 합니다.range={0} ~ {1} 범위를 허용합니다.max={0} 까지 허용합니다.
객체 오류와 필드 오류로 나누고 그 안에서 범용성에 따라 레벨을 나눈다.
required.item.itemName
required.itemName
required.java.lang.String
required
메시지 코드는 이렇게 생성된다. 메시지에 1번이 없으면 2번으로, 2번이 없으면 3번을 찾는다.
ValidationUtils
before
if (!StringUtils.hasText(item.getItemName())) {bindingResult.rejectValue("itemName","required","기본: 상품 이름은 필수입니다.");}
price 필드에 문자를 넣으면 typeMismatch를 설정해준 적이 없는데 이렇게 찍힌다. 스프링은 타입 오류가 발생하면 직접 생성해준다.
Failed to convert property value of type java.lang.String to required type
java.lang.Integer for property price; nested exception is
java.lang.NumberFormatException: For input string: "A"