Spring 검증 3

바그다드·2023년 5월 10일
0

검증

목록 보기
3/5
  • 지난 포스팅에서 BindingResult라는 객체를 활용하여 에러 메세지를 처리하였다. 그런데 문제는 파라미터가 많다는 것이고, 메세지를 바꿔야 할 경우 코드를 다시 하나하나 바꿔야 한다는 것이다.
    이번 포스팅에서는 MessageSource를 이용하여 에러 메세지를 따로 관리하고, 코드를 좀 더 간결하게 바꿔보자.

MessageSource생성

  • errors.properties를 생성하고 아래의 값을 넣어주자
#required.item.itemName=상품 이름은 필수입니다.
#range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
#max.item.quantity=수량은 최대 {0} 까지 허용합니다.
#totalPriceMin=가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
#==ObjectError==
#Level1
totalPriceMin.item=상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
#Level2 - 생략
totalPriceMin=전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}
#==FieldError==
#Level1
required.item.itemName=상품 이름은 필수입니다.
range.item.price=가격은 {0} ~ {1} 까지 허용합니다.
max.item.quantity=수량은 최대 {0} 까지 허용합니다.
#Level2 - 생략
#Level3
required.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} 까지의 숫자를 허용합니다.
#Level4
required = 필수 값 입니다.
min= {0} 이상이어야 합니다.
range= {0} ~ {1} 범위를 허용합니다.
max= {0} 까지 허용합니다.

BindingResult.addError()

        if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
            bindingResult.addError(new FieldError("item", "price", item.getPrice(), false, new String[]{"range.item.price", "range.default"}, new Object[]{"1000","1000000"}, null));
        }
        // 특정 필드가 아닌 복합 룰 검증
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                // 특정 필드 에러가 아닌 글로벌 에러이므로 ObjectError로 담아줌
                bindingResult.addError(new ObjectError("item",new String[]{"totalPriceMin"}, new Object[]{10000, resultPrice}, "가격 * 수량의 합은 10,000원 이상이어야 합니다. 현재 값 = " + resultPrice));
            }
        }
  • 먼저 addError메서드를 이용해 값을 담는 방법이다. 앞선 포스팅에서는 디폴트 메세지만 지정을 했지만, 이번에는 properties파일을 활용하여 메세지를 따로 관리할 수 있다.

FieldError

  • 필드와 관련된 에러 메세지를 띄울 때 사용하며
    FieldError를 활용한다.

  • addError(new FieldError(objectName, field, rejectedValue, bindingFailure, codes, arguments, defaultMessage))
    이런 순서로 파라미터를 지정해줘야 하는데, 각 파라미터에는 다음과 같은 값이 들어간다
    objectName : 오류가 발생한 객체 이름
    field : 오류 필드
    rejectedValue : 사용자가 입력한 값(거절된 값)
    bindingFailure : 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
    codes : 메시지 코드
    arguments : 메시지에서 사용하는 인자
    defaultMessage : 기본 오류 메시지

  • codes에는 문자타입의 배열({"range.item.price", "range.default"})이 들어가는 것을 알 수 있는데, 이는 에러 코드의 우선순위로 "range.item.price"라는 키 값이 errors.properties에 존재한다면 그 값을 띄우고 존재하지 않는다면 "range.default"를 띄운다.

  • arguments에는 Object타입의 배열이 들어간다.

ObjectError

  • 필드가 아닌 범위의 에러 메세지를 띄울 때 사용하며,
    ObjectError를 활용한다.
  • addError(new ObjectError(objectName,codes, arguments, defaultMessage))
    objectName : 오류가 발생한 객체 이름
    codes : 메시지 코드
    arguments : 메시지에서 사용하는 인자
    defaultMessage : 기본 오류 메시지
  • 이 방법을 이용해 메세지를 관리할 수는 있게 되었지만, 파라미터가 너무 많아 복잡하기도 하고, 메세지를 가져다 쓰는 것도 복잡한 문제가 있다.
    이미 BindingResult는 타겟이 되는 객체를 알고 있기 때문에 굳이 파라미터 값으로 타겟의 이름을 넘겨주지 않아도 된다. 코드로 확인하자.

BindingResult.rejectValue()

		// FieldError(필드에러)
  		if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
        bindingResult.rejectValue("price", "range", new Object[]{1000, 1000000},null
              );
        }
        
        // ObjectError(글로벌 에러)
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                // 특정 필드 에러가 아닌 글로벌 에러이므로 ObjectError로 담아줌
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }
  • bindingResult.reject(errorCode, errorArgs, defaultMessage)
    errorCode : 오류 코드
    errorArgs : 오류 메시지의 파라미터를 치환하기 위한 값
    defaultMessage : 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지

  • bindingResult.rejectValue(field, errorCode, errorArgs, defaultMessage)
    field : 오류 필드명
    errorCode : 오류 코드
    errorArgs : 오류 메시지의 파라미터를 치환하기 위한 값
    defaultMessage : 오류 메시지를 찾을 수 없을 때 사용하는 기본 메시지

  • errorCode는 앞서 codes와 달리 키값의 최상위 키워드만 명시되어 있는데, 이는 messageResolver에서 특정한 규칙을 이용해 키값을 생성하고, errors.properties에서 키값이 매칭되는 것이 있다면 그 값을 가지고 온다.

MessageCodesResolver

  • MessageCodesResolver는 인터페이스이고 구현체로 주로 DefaultMessageCodesResolver를 사용하는데 DefaultMessageCodesResolver의 기본 메세지 생성 규칙은 다음과 같다.
  • DefaultMessageCodesResolver의 기본 메세지 생성 규칙
  1. 객체 오류
    1순위: code + "." + object name
    2순위: code
    예를 들어, 에러 메세지가 required이고, object name이 item이라면
    1순위: required.item
    2순위: required
    이런 식으로 에러 메세지가 생성된다.
  2. 필드 오류
    1순위: code + "." + object name + "." + field
    2순위: code + "." + field
    3순위: code + "." + field type
    4순위: code
    예를 들어, 에러 메세지가 required이고, object name이 item, field가 itemName이라면
    1순위: required.item.itemName
    2순위: required.itemName
    3순위: required.java.lang.String
    4순위: required
    이런 식으로 에러 메세지가 생성된다.
  • 구체적인 것이 우선순위가 높은데, 만약 생성된 메세지와 매칭되는 키값이 없다면, 후순위의 메세지와 매칭을 시도한다.
  • 이런 방식으로 검증에 실패할 때 띄우는 메세지를 관리할 수 있게 되었다

    그런데, 타입 에러로 인한 바인딩 에러는 여전히 스프링이 제공하는 기본 메세지가 뜬다.

스프링 기본 메세지 관리하기

에러 메세지 추가하기

  • errors.properties에 코드를 추가해주자
#추가
typeMismatch.java.lang.Integer=숫자를 입력해주세요.
typeMismatch=타입 오류입니다.

  • 이제 스프링이 제공하는 기본 메세지도 다른 메세지로 대체해서 띄울 수 있게 되었다.
  • 그런데 문제는 검증 역할이 검증 성공 로직보다 훨씬 많다.
    다음 포스팅에서 이 문제를 개선해보자.

출처 : 김영한님의 스프링MVC 2편

profile
꾸준히 하자!

0개의 댓글