커스텀 Validator로 유효성 검사하기

이원찬·2023년 12월 25일
0

Spring

목록 보기
5/13
post-custom-banner

Exception은 핸들러로 컨트롤 하자

위 링크에서 Exception 컨트롤러를 이용해 MethodArgumentNotValidException 을 처리하는 코드를 적고 오자

Validation의존성을 추가하여 유효성 검사를 쉽게 하자

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

커스텀 Validator와 Annotation을 만들어 보자

먼저 Annotation부터

만약 카테고리가 존재하는지 확인하는 유효성 검사라면

@Documented
@Constraint(validatedBy = CategoriesExistValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExistCategories {

    String message() default "해당하는 카테고리가 존재하지 않습니다.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

저기 위에 보면 Validator에 빨간줄이 뜰것이다.

커스텀 Validator를 만들어 보자

@Component
@RequiredArgsConstructor
// ConstraintValidator를 구현하여야한다.
public class CategoriesExistValidator implements ConstraintValidator<ExistCategories, List<Long>> {

		// FoodCategoryCommandService에 선언해 놓은 isExist함수를 사용하기 위해 주입받는다.
    private final FoodCategoryCommandService foodCategoryCommandService;

		// 이건 똑같이 사용된다.
    @Override
    public void initialize(ExistCategories constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

		// 이 부분에서 유효성 검사가 이뤄진다.
    @Override
    public boolean isValid(List<Long> values, ConstraintValidatorContext context) {

				// 존재 한다면 유효하다는 거고
        Boolean isValid = foodCategoryCommandService.isExist(values);

				// 존재 하지 않는다면 
        if (!isValid) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(ErrorStatus.FOOD_CATEGORY_NOT_FOUND.toString()).addConstraintViolation();
        }

        return isValid;

    }
}

CategoriesExistValidator라는 클래스를 통해 @ExistCategories가 붙은 대상을 검증한다.

CategoriesExistValidator

  • ExistCategories 어노테이션에 대한 로직을 담을 것이며 검증 대상이 List<Long>임을 명시한다.

  • ConstraintValidator에 추상메서드로 되어있는 메서드를 구현한다.

  • 우리는 isValid만 구현하면 된다.

  • isValid함수에서 검증 대상인 List 의 값을 가진 카테고리가 모두 데이터베이스에 있는 지를 판단하고 없을 경우 false를 반환

  • 만약 false라면 if 문에서 ConstraintViolationException 예외를 발생!

  • Repository는 Service레이어만 접근하게끔 한다!! (따라서 Service에서 isExist 메서드를 만든다!!)

  • ConstraintViolationException 예외를 발생시키지만 Controller에 붙어있는 @Valid 어노 테이션 때문에 MethodArgumentNotValidException 이 발생!!!!

  • 그러면 ExceptionAdvice가 잡아서

    @Override
    public ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException e, HttpHeaders headers
            , HttpStatus status, WebRequest request
    ) {
        Map<String, String> errors = new LinkedHashMap<>();
    
        e.getBindingResult().getFieldErrors().stream()
                .forEach(fieldError -> {
                    String fieldName = fieldError.getField();
                    String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse("");
                    errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage);
                });
    
        return handleExceptionInternalArgs(e,HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"),request,errors);
    }

    위 코드를 실행한다!!! 그 다음은 API 응답 통일과 비슷

    Controller에서 Annotation을 직접접 명시 하여 유효성을 검사한다.

    1. RequestBody를 유효성 검사 할때는 아래 처럼 @Valid 를 명시하여 유효성을 검사한다.

      @PostMapping
          public ApiResponse<MovieResponseDTO.CreateResultDTO> createMovie(@RequestBody @Valid MovieRequestDTO.CreateDTO request) {
              return ApiResponse.onSuccess(MovieConverter.toCreateResultDTO(movieCommandService.createMovie(request)));
          }

    2. PathVariable , RequestParam 를 유효성 검사 할때는 아래 처럼 Controller에서 @Valiated 를 적고 인자에 커스텀 어노테이션을 명시하여 유효성을 검사한다.

      @PatchMapping("{movieId}")
      public ApiResponse<MovieResponseDTO.UpdateResultDTO> updateMovie(@PathVariable @ExistMovie Long movieId, @RequestBody @Valid MovieRequestDTO.UpdateDTO request) {
          return ApiResponse.onSuccess(MovieConverter.toUpdateResultDTO(movieCommandService.updateMovie(movieId, request)));
      }

profile
소통과 기록이 무기(Weapon)인 개발자
post-custom-banner

0개의 댓글