[TIL] #5. 검증2 - Bean Validation ①

kiteB·2021년 9월 28일
0

TIL-Spring4

목록 보기
9/17
post-thumbnail

Bean Validation - 소개

지난 시간에 개발했던 검증 로직은 다음과 같은 특징을 갖는다.

  • 검증 기능을 매번 코드로 작성하는 것은 번거로운 과정이다.
  • 특정 필드에 대한 검증 로직은 매우 일반적인 로직이다.
    • 빈 (null) 값인지 체크
    • 범위를 넘는지 체크

이런 검증 로직을 모든 프로젝트에 적용할 수 있게 공통화/표준화한 것이
Bean Validation이다.

Bean Validation

특정한 구현체가 아닌, Bean Validation 2.0 (JSR-380)이라는 기술 표준

Bean Validation은 검증 애노테이션과 여러 인터페이스의 모음으로,
이를 통해 애노테이션 하나로 검증 로직을 매우 편리하게 적용할 수 있다.


Bean Validation - 시작

우리 프로젝트에 적용하기 전에,
순수한 Bean Validation 사용법에 대해 먼저 알아보자!

Bean Validation 의존관계 추가

Bean Validation을 사용하려면 의존관계를 추가해야 한다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

Item - Bean Validation 애노테이션 적용

@Data
public class Item {

 private Long id;
 
 @NotBlank
 private String itemName;
 
 @NotNull
 @Range(min = 1000, max = 1000000)
 private Integer price;
 
 @NotNull
 @Max(9999)
 private Integer quantity;

...
 
}

검증 애노테이션

  • @NotBlank: 빈값 + 공백만 있는 경우를 허용하지 않는다.
  • @NotNull: null을 허용하지 않는다.
  • @Range(min = 1000, max = 1000000): 범위 안(1000 이상, 1000000 이하)의 값이어야 한다.
  • @Max(9999): 최대 9999까지만 허용한다.

테스트 코드 작성

🔗 전체 코드 확인하기

실행 결과


Bean Validation - 프로젝트 준비 V3

🔗 코드 확인하기

실행 결과


Bean Validation - 스프링 적용

✔ 스프링 MVCBean Validator 사용법

스프링 부트는 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다!

✔ 스프링 부트는 자동으로 글로벌 Validator로 등록한다.

  • 글로벌 Validator가 적용되어 있기 때문에, @Valid,
    @Validated만 적용하면 된다.
  • 검증 오류가 발생하면,
    FieldError, ObjectError를 생성해서 BindingResult에 담아준다.

📌 검증 순서

  1. @ModelAttribute 각각의 필드에 타입 변환 시도
    • 성공하면 다음으로
    • 실패하면 typeMismatchFieldError 추가
  2. Validator 적용

⭐ 바인딩에 성공한 필드만 Bean Validation을 적용한다.

🔗 전체 코드 확인하기

실행 결과


Bean Validation - 에러 코드

Bean Validation이 기본으로 제공하는 오류 메시지 변경

만약 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

메시지 등록

이제 typeMismatch처럼, 우리가 원하는 형태로 출력되도록 메시지를 등록해보자.

errors.properties

#Bean Validation 추가
NotBlank={0} 공백X 
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
  • {0}은 필드명
  • {1}, {2} ...은 각 애노테이션 마다 다르다.

Bean Validation 메시지 찾는 순서

  1. 생성된 메시지 코드 순서대로 messageSource에서 메시지 찾기
  2. 애노테이션의 message 속성 사용 → @NotBlank(message = "공백! {0])
  3. 라이브러리가 제공하는 기본 값 사용 → 공백일 수 없습니다.

🔗 전체 코드 확인하기

실행 결과


Bean Validation - 오브젝트 오류

이번에는 특정 필드(FieldError)가 아닌 오브젝트 관련 오류(ObjectError) 처리 방법에 대해 알아보자!

1. @ScriptAssert

@ScriptAssert()를 사용하여 해결할 수 있다!

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

실행해보면 정상적으로 수행되며, 메시지 코드도 다음과 같이 생성된다.

ScriptAssert.item
ScriptAssert

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

그래서 오브젝트 오류(글로벌 오류)의 경우는 @ScriptAssert을 억지로 사용하는 것보다는
다음과 같이 오브젝트 오류 관련 부분만 직접 자바 코드로 작성하는 것을 권장한다.

@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);
        }
    }   
}

실행 결과

profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글