Validation V4에서는 V3에서 해당 필드에 에러메세지를 직접 default메세지를 직접 기입한 것을 따로 에러 메세지를 모아놓은 errors.properties파일에 모아두어 불러오는 방법을 소개하려고합니다.
resources/errors.properties
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
application.properties
spring.messages.basename=errors
V4 - 1
@PostMapping("/add")
public String addItemV3(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes , Model model) {
//검증 로직
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.getQuantity()==null|| item.getQuantity() >= 9999) {
bindingResult.addError(new FieldError("item","quantity",item.getQuantity(),
false,new String[]{"max.item.quantity"},new Object[]{9999}, 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[]{1000,resultPrice},null));
}
}
//검증에 실패하면 다시 입력 폼으로
if(bindingResult.hasErrors()){
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에선 에러메세지를 defaultMessage에 등록한 모습이엿는데 이번에는 중간에 String[] 안에 errors.properties파일에 key값을 넣은 모습을 볼 수 있다.
FieldError
public FieldError(java.lang.String objectName, java.lang.String field, @org.springframework.lang.Nullable java.lang.Object rejectedValue, boolean bindingFailure, @org.springframework.lang.Nullable java.lang.String[] codes, @org.springframework.lang.Nullable java.lang.Object[] arguments, @org.springframework.lang.Nullable java.lang.String defaultMessage)
objectName : 입력폼에서 담겨온 ModelAttribute에 값
field : 해당 objectName에서 담긴 각 필드(컬럼)
rejectedValue : 필드에 담아온 값을 보존하기 위해 필드값
codes : error.properties에 저장된 에러 코드의 key값
arguments : 해당 에러코드에 담길 매개변수
defaultMessage : 없다면 기본 보여줄 에러메세지
ObjectError
public ObjectError(String objectName, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage)
들어가는 인자가 FieldError에 내용에서 몇개 줄어는 모습이고 내용은 일치한다. ObjectError는 특정 필들(컬럼)에 할당되는게 아니므로 objectName에서 globalErrors에 담기게 되서 직접 에러메세지에 관련된 인자만 들어간 모습이다.
V4 - 2
컨트롤러에서 BindingResult인자는 target 다음에 와야한다고 설명을 한적이 있다. 그렇기에 bindingResult는 에러를 체크해야할 target을 알고있다는 뜻이된다. 그렇기에 FieldError나 objectError보다 더 간결한 방법으로 해당 target이나 필드에 에러코드를 담을수 있다.
@PostMapping("/add")
public String addItemV4(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes , Model model) {
//검증 로직
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.getQuantity()==null|| item.getQuantity() >= 9999) {
bindingResult.rejectValue("quantity","max",new Object[]{9999},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);
}
}
//검증에 실패하면 다시 입력 폼으로
if(bindingResult.hasErrors()){
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나 objectError가 rejectValue와 reject로 바뀐 모습이다.
rejectValue
void rejectValue(@org.springframework.lang.Nullable java.lang.String s, java.lang.String s1, @org.springframework.lang.Nullable java.lang.Object[] objects, @org.springframework.lang.Nullable java.lang.String s2);
s : ModelAttirbute에서 담긴 객체에 필드(컬럼)
s1 : 앞서 bindingResult에는 target을 알고있다고 설명한 적이 있습니다. 여기에 적힌 값은 뒤에 target이름(item)과 s(필드)가 붙으며 필드까지 붙은 값을 error.properties에 key로 만들어 에러코드를 불러온다. ( ex. required.item.itemName )
objects : s1에서 가져온 에러코드에 담길 매개변수
s2 : defaultMessage
reject
void reject(java.lang.String s, @org.springframework.lang.Nullable java.lang.Object[] objects, @org.springframework.lang.Nullable java.lang.String s1);
s : error.properties에 등록한 key값
object : 에러코드에 들어갈 매개변수
s1 : defaultMessage
V4 - 3
V4에서 설명한 에러코드는 모두 error.properties에서 불러오는 방식을 설명을 했습니다.
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
그런데 이렇게 모든 에러코드의 key 이름이 구체적이라면 해당 에러코드는 범용성있게 사용하기 어려울 것입니다.
required=필수값 입니다.
이런식으로 간결하면 어디에나 쉽게 사용이 될 수 있겠지만 특정 페이지에선 자세한 에러설명이 나와야한다면 위의 에러코드는 사용하지 못할 것입니다.
required.item.itemName=상품 이름은 필수입니다.
required=필수값 입니다.
error.properties에 이렇게 두개의 에러코드가 들어있고
bindingResult.rejectValue("itemName","required");
위처럼 에러코드를 불러온다면 첫번째 에러코드를 불러오게 될 것입니다. 앞서 rejectValue는 target과 필드에 대한 정보가 뒤에 더 붙게 되어 "required.item.itemName"이 키 값이 되어 에러코드를 찾는다고 설명했지만 결국 에러코드는 두번째 인자인 "required"도 키값이 될수 있습니다. 그렇지만 첫번째 에러코드를 불러오는 이유는 더 자세한 이름을 불러오는게 우선이기 때문입니다. 처음엔 범용성이 있는 짧은 에러코드만 작성하더라도 추후에 자세한 에러코드가 필요하다면 이런식으로 직접 자세한 에러코드를 만들어 즉각반영이 가능합니다.
다음 V5에서 입력받은 값들의 타입이 맞지 않을때의 오류를 처리하는 글을 작성해 보겠습니다.
'spring' 카테고리의 다른 글
Validation 분리 (0) | 2024.05.15 |
---|---|
Validation V5 (0) | 2024.05.15 |
Validation V3 (0) | 2024.05.14 |
Validation V2 (0) | 2024.05.13 |
Validation V1 (2) | 2024.05.12 |