SPRING/Spring MVC

[SpringMVC] 검증(Validation)(3) - BindingResult ( rejectValue 메소드 )

IT록흐 2023. 8. 28. 21:10
반응형
 

[SpringMVC] 요청데이터 검증(Validation)하기(2) - BindingResult

[SpringMVC] 요청데이터 검증(Validation)하기(1) 클라이언트로부터 요청(Request)이 들어오면, 요청 데이터는 가장 먼저 Controller에 도착한다. 그러므로 Controller에서 적합한 데이터인지 검사해야 한다. 적

lordofkangs.tistory.com

 

 

지난 포스팅에서 BindingResult를 다루어 보았다. 

 

BindingResult는 오류 메시지를 담는 그릇으로, FieldError와 ObjectError에 담긴 에러 메시지를 저장한다. 그러나 개발자가 FieldError와 ObjectError를 직접 생성하면 코드가 복잡해지고 길어진다. 그래서 BindingResult는 FieldError, ObjectError를 대신 생성하는 기능을 제공하는데, 그것이 rejectValue 메소드이다. 

 

 

rejectValue 메소드

 

 

rejectValue 메소드는 3가지 형태로 제공된다. 가장 디테일한 메소드를 분석해보자.

 

메소드의 파라미터는 아래와 같다. 

 

String field : 바인딩되는 객체의 필드명

String errorCode : 오류를 지칭하는 코드명

Object[] errorArgs : 오류 메시지 생성에 필요한 Argument

String defaultMessage : 디폴트 오류 메시지

 

FieldError, ObjectError를 사용할 때보다 단순해진 부분은 오류코드(errorCode) 부분이다. 

 

그럼 어느 부분이 단순해졌을까?

 

// FieldError 사용
bindingResult.addError(new FieldError("item","price",item.getPrice(),false,new String[]{"range.item.price","range.item","range"},new Object[]{1000,1000000},null));

// rejectValue 메소드
bindingResult.rejectValue("price","range",new Object[]{1000,100000000},null);

 

1. 사라진 객체명

 

BindingResult는 Request로 들어온 요청데이터를 @ModelAttribute가 선언된 객체와 바인딩 할 때 발생하는 오류를 담는 저장소이다. 그러므로 @ModelAttribute가 선언된 객체명을 파라미터로 입력해주어야 하는데, BindingResult는 @ModelAttribute로 선언된 객체와 1대1 관계인데 굳이 파라미터로 넘겨줄 이유가 없다. 

 

FieldError와 같이, BindingResult와 집합관계인 경우에는 @ModelAttribute로 선언된 객체명을 입력해주어야 했다. 그러나 rejectValue 메소드 안으로 FieldError가 들어와 BindingResult와 의존관계를 맺는 경우에는 FieldError도 @ModelAttribute로 선언된 객체와 1대1관계가 된다. 그러므로 굳이 객체명을 입력해 줄 필요가 없어진다. 

 

2. 사라진 오류 데이터

 

오류가 발생한 데이터를 파라미터로 넘겨주어야 했지만, 1번과 마찬가지로 BindingResult가 FieldError와 의존관계가 되어 내부로 들어왔으므로, 굳이 파라미터로 전달할 필요가 사라진다. 

 

3. 사라진 바인딩 오류 여부

 

FieldError에 담기는 오류가 바인딩 관련 에러이면 true, 유효성 검사 같은 에러이면 false를 넣어주어야 한다. 그런데 바인딩에서 발생하는 오류는 어차피 Spring이 자동으로 BindingResult에 담는다. 그러므로 개발자가 rejectValue를 호출하여 에러를 저장할 때는 유효성검사에서 발생한 오류인 경우이다. 그러므로 굳이 false, true를 넘겨줄 필요가 없다. 

 

 4. 축약된 에러코드

 

FieldError는 오류 메시지가 저장된 파일에서 오류메시지를 가져와 저장한다.  이때 다양한 메시지를 저장할 수 있도록 String 배열 형태로 저장해서 길이가 길어진다.

 

new String[]{"range.item.price","range.item","range"} 

 

rejectValue 메소드는 에러코드 범위가 넓은 코드만 넣어주면 된다.  range.item.price라면 range를 에러코드로 넣어준다. 그러면 BindingResult가 오류메시지 파일에서 range로 시작하는 메시지 중 가장 디테일한 코드를 우선으로 가져온다. range보다는 range.item이 디테일하고 range.item 보다는 range.item.price가 디테일하다. 

 

그러므로 new String[]{"range.item.price","range.item","range"} 같이 모두 배열로 넘길 필요가 없다. BindingResult가 알아서 range.item.price 탐색하고 없으면 range.item를, 없으면 range를 탐색하여 메시지를 가져온다. 

 

 

정리하면,

 

개발자가 FieldError와 ObjectError를 직접 생성하여 BindingResult에 주입하면 BindingResult와 FieldError는 집합관계가 되어 파라미터가 복잡해진다. 만약  BindingResult의 rejectValue 메소드가 호출 될 때, FieldError를 생성하면 BindingResult와 FieldError와 의존관계가 되고 FieldError와  @ModelAtttribute로 선언된 객체가 1대1로 매핑되므로, 개발자가 메소드로 넘겨야 하는 파라미터의 개수가 줄어든다. 이런 원리와 더불어 여러가지 기능이 자동화되어, BindingResult가 제공하는 rejectValue 메소드는 단순한 형태로 오류 메시지를 저장할 수 있는 서비스를 제공한다.

 

 

 FieldError, ObjectError를 사용한 경우

@PostMapping("/add") 
public String addItem(@ModelAttribute Item item, BindingResult bindingResult) {


    // 1. 상품명이 공백인 경우
    if(!StringUtils.hasText(item.getItemName())){
        bindingResult.addError(new FieldError("item","itemName","상품이름은 필수 입니다."));
    }

    // 2. 가격이 범위 밖인 경우
    if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000){
        bindingResult.addError(new FieldError("item","price","가격은 1,000 ~ 1,000,000 까지 허용합니다. "));
    }

    // 3. 수량이 범위 밖인 경우
    if(item.getQuantity() == null || item.getQuantity() >= 9999){
        bindingResult.addError(new FieldError("item","quantity","수량은 최대 9,999까지 혀용합니다. "));
    }

    // 4. 두 개 항목 이상의 복합 룰 검증
    if(item.getPrice() != null && item.getQuantity() != null){
        int resultPrice = item.getPrice()*item.getQuantity();
        if(resultPrice < 10000){
            bindingResult.addError(new ObjectError("item","가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재값 = " + resultPrice));
        }
    }

    // 중략 ...


}

 

 

BindingResult의 rejectValue 메소드를 사용한 경우

@PostMapping("/add") 
public String addItemV4(@ModelAttribute Item item, BindingResult bindingResult) {

//중략...

    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,100000000},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);
        }
    }    
    
    //중략...


}

 

 

반응형