본문 바로가기
스프링/스프링 웹 개발 활용

[Spring] Bean Validation 에러 코드 및 오브젝트 오류 처리

by drCode 2023. 10. 10.
728x90
반응형

Bean Validation이 기본으로 제공하는 오류 메시지를 좀 더 자세히 변경하고 싶으면 어떻게 하면 될까?

 

Bean Validation을 적용하고 bindingResult 에 등록된 검증 오류 코드를 보면,

오류 코드가 애노테이션 이름으로 등록된다. 마치 typeMismatch 와 유사하다.

 

NotBlank 라는 오류 코드를 기반으로 MessageCodesResolver 를 통해 다양한 메시지 코드가 순서대로 생성된다.

 

@NotBlank

 - NotBlank.item.itemName

 - NotBlank.itemName

 - NotBlank.java.lang.String

 - NotBlank

 

@Range

 - Range.item.price

 - Range.price

 - Range.java.lang.Integer

 - Range

 

errors.properties에 메시지를 등록하면 아래의 항목과 같다.

#Bean Validation 추가
NotBlank={0} 공백X 
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}

{0} 은 필드명이고, {1} , {2} ...은 각 애노테이션 마다 다르다.

 

실행 결과

 

실행

실행해보면 방금 등록한 메시지가 정상 적용되는 것을 확인할 수 있다.

 

errors.properties 에 메시지를 우선순위가 먼저인 메시지를 추가하면 우선순위가 먼저인 메시지가 출력된다.

#Bean Validation 추가
NotBlank.item.itemName=상품 이름을 적어주세요.

NotBlank={0} 공백X 
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}

BeanValidation 메시지 찾는 순서

1. 생성된 메시지 코드 순서대로 messageSource 에서 메시지 찾기

2. 애노테이션의 message 속성 사용 → @NotBlank(message = "공백! {0}")

3. 라이브러리가 제공하는 기본 값 사용 → 공백일 수 없습니다.

 

애노테이션의 message 사용 예

@NotBlank(message = "공백은 입력할 수 없습니다.")
private String itemName;

 

 

Bean Validation - 오브젝트 오류

Bean Validation에서 특정 필드( FieldError )가 아닌 해당 오브젝트 관련 오류( ObjectError )는 어떻게 처리할 수 있을까?

 

다음과 같이 @ScriptAssert() 를 사용하면 된다.

@Data
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000")
public class Item {
    //...
}

실행해보면 정상 수행되는 것을 확인할 수 있다. 메시지 코드도 다음과 같이 생성된다.

 

메시지 코드

 - ScriptAssert.item

 - ScriptAssert

 

그런데 실제 사용해보면 제약이 많고 복잡하다. 그리고 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우들도 종종 등장하는데, 그런 경우 대응이 어렵다.

 

따라서 오브젝트 오류(글로벌 오류)의 경우 @ScriptAssert 을 억지로 사용하는 것 보다는

다음과 같이 오브젝트 오류 관련 부분만 직접 자바 코드로 작성하는 것을 권장한다.

 

ValidationItemControllerV3 - 글로벌 오류 추가

@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {

    // 특정 필드가 아닌 복합 룰 검증
    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()) {
        log.info("errors = {} ", bindingResult);
        // bindingResult 는 model Attribute에 안담아도 자동으로 view로 넘겨준다
        return "validation/v3/addForm";
    }
    
    //성공 로직
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v3/items/{itemId}";
}

@ScriptAssert 부분 제거

 

 

728x90
반응형

댓글