[Spring/Springboot] @Valid 란?

2024. 12. 23. 00:00CS/Spring

@Valid

  • 스프링에서 입력 데이터의 유효성(Validation)을 자동으로 검사
    • 클라이언트가 보낸 데이터를 확인해서 규칙에 맞지 않으면 자동으로 오류를 알려줌

1. DTO 클래스에서 사용

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;

public class SignUpRequestDTO {
    @NotNull(message = "이름은 필수")
    private String name;
    
    @NotNull(message = "이메일은 필")
    @Email(message = "유효한 이메일 주소를 입력")
    private String email;
}
//각각 @NotNull, @Email(이메일형식)이 검사를 통과하지 못하면, 오류 발생

2. 컨트롤러에서 사용

  • 미션으로 직접 작성한 코드를 통해서 이해하기 → 컨트롤러에서 호출하고 오류 발생 메세지 응답까지 흐름 살펴보기
@PostMapping("/")
public ApiResponse<StoreResponseDTO. addStoreToSpecificRegionResultDto> addStore(@RequestBody @Valid StoreRequestDTO.addStoreToSpecificRegionDto request){
    Store store = storeCommandService.addStoreToSpecificRegion(request);
    return ApiResponse.onSuccess(StoreConverter.toAddStoreResultDto(store));
}
  • 클라이언트 요청 전송 -> 클라이언트가 addStore API에 POST 요청을 보냄
  • 요청 본문에 StoreRequestDTO.addStoreToSpecificRegionDto 형태의 데이터를 포함 ->regionName 필드가 포함됨
  • addStore 메서드는 @Valid 어노테이션을 통해 StoreRequestDTO.addStoreToSpecificRegionDto 데이터의 유효성을 검사

 

@Documented
@Constraint(validatedBy = RegionExistValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExistRegion {
    String message() default "해당 지역이 존재하지 않습니다.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default  {};
}

 

  • 특히, regionName 필드에는 @ExistRegion 어노테이션이 적용되어 있으므로 RegionExistValidator가 실행

 

@Component
@RequiredArgsConstructor
public class RegionExistValidator implements ConstraintValidator<ExistRegion, String> {
//RegionExistValidator는 ConstraintValidator 인터페이스를 구현하여 @ExistRegion 어노테이션을 해석**

    private final RegionService regionService;

    @Override
    public void initialize(ExistRegion constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String regionName, ConstraintValidatorContext context) {
    //isValid 메서드가 호출되면서 regionName 값이 RegionService의 existsByName 메서드를 통해 존재하는지 확인

        boolean isValid = regionService.existsByName(regionName);

        if (!isValid) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(ErrorStatus.REGION_NOT_FOUND.toString()).addConstraintViolation();
        }
        return isValid;
       
    }
    //RegionService.existsByName(regionName)가 true를 반환하면 isValid 메서드는 true를 반환하며 검증을 통과
    //false일 경우, 검증이 실패하며 오류 메시지로 ErrorStatus.REGION_NOT_FOUND가 설정
}
@PostMapping("/")
public ApiResponse<StoreResponseDTO. addStoreToSpecificRegionResultDto> addStore(@RequestBody @Valid StoreRequestDTO.addStoreToSpecificRegionDto request){
    Store store = storeCommandService.addStoreToSpecificRegion(request);
    return ApiResponse.onSuccess(StoreConverter.toAddStoreResultDto(store));
}
//유효성 검사를 통과하면 addStore 메서드는 다음 단계로 진행
//만약 검증이 실패하면, @Valid는 예외를 발생시킴 -> 스프링은 해당 예외를 처리하여 클라이언트에게 유효성 검사 실패 응답을 반환