위 링크에서 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을 직접접 명시 하여 유효성을 검사한다.
RequestBody를 유효성 검사 할때는 아래 처럼 @Valid
를 명시하여 유효성을 검사한다.
@PostMapping
public ApiResponse<MovieResponseDTO.CreateResultDTO> createMovie(@RequestBody @Valid MovieRequestDTO.CreateDTO request) {
return ApiResponse.onSuccess(MovieConverter.toCreateResultDTO(movieCommandService.createMovie(request)));
}
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)));
}