Custom validation with database
데이터베이스의 값을 사용해서 커스텀 유효성 검사
- 어노테이션 생성 및 ConstraintValidator 구현체 생성
ActiveUniqueId.kt @Target(AnnotationTarget.FIELD)@Retention(AnnotationRetention.RUNTIME)@Constraint(validatedBy = [ActiveUniqueIdValidator::class])annotation class ActiveUniqueId (val message: String = "잘못된 ID입니다.",val groups: Array<KClass<*>> = [],val payload: Array<KClass<out Payload>> = [],)@Componentclass ActiveUniqueIdValidator(private val uniqueIdMapper: UniqueIdMapper) : ConstraintValidator<ActiveUniqueId, String> {override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {//필수입력 여부는 별도의 NotBlank 어노테이션으로 제약을 설정한다.if (value === null || value.isBlank()) {return true}return uniqueIdMapper.selectUniqueIdCnt(value) > 0}}ActiveUniqueId.java - 요청 DTO 클래스의 멤버에 제약 설정
any request dto class @field:Schema(description = "ID")@field:NotBlank@field:ActiveUniqueIdvar uniqueId: String,any request dto class - Controller 메소드에 Valid(Validated) 어노테이션 추가
any controller class @Operation(summary = "커스텀 유효성 체크 with 데이터베이스 테스트", description = "커스텀 유효성 체크 with 데이터베이스 테스트 API")@PostMapping("/custom-validation-with-database-test")fun testCustomValidationWithDatabase(@Valid @RequestBody param: CustomValidationWithDatabaseRequestDto): ResponseEntity<ResponseDto<Unit>> {log.info("testCustomValidationWithDatabase param: $param")return ResponseEntity.status(HttpStatus.CREATED).body((ResponseDto(Unit)))}any controller class - Validation 오류가 발생하면 MethodArgumentNotValidException 예외가 던져지는데, 이 예외가 발생시 405 Bad Request 오류가 발생하도록 설정
RestApiExceptionAdvice.kt @ExceptionHandler(MethodArgumentNotValidException::class)@ResponseBodyfun methodArgumentNotValidExceptionHandler(e: MethodArgumentNotValidException): ResponseEntity<ResponseDto<Unit>> {log.error("### MethodArgumentNotValidException occurred", e)val anyItem = e.bindingResult.fieldErrors[0]return ResponseEntity.badRequest().body(ResponseDto(ApiResultCode.WRONG_PARAMETER, String.format("%s (%s)", anyItem.defaultMessage, anyItem.field)))}RestApiExceptionAdvice.java @ExceptionHandler(MethodArgumentNotValidException.class)public @ResponseBody ResponseEntity<ResponseDto<Void>> methodArgumentNotValidExceptionHandler(final MethodArgumentNotValidException e) {log.error("### MethodArgumentNotValidException Occurred. ID: [{}]", ServletUtil.getRequestAttribute(ServletUtil.X_REQUEST_ID), e);return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON).body(new ResponseDto<>(ApiResultCode.INVALID_PARAMETER));} - 테스트
any test class @DisplayName("커스텀 유효성 체크 with 데이터베이스 테스트")@Testfun `커스텀 유효성 체크 with 데이터베이스 테스트`() {val param = CustomValidationWithDatabaseRequestDto("not-unique-id")return Given {spec(jsonRequestSpecification)body(param)} When {post("/spring-boot/custom-validation-with-database-test")} Then {statusCode(HttpStatus.SC_BAD_REQUEST)} Extract {println(body().asString())}}any test class - 로그 확인
2026-02-14 13:56:38,596 [DEBUG] [http-nio-auto-1-exec-3] n.s.l.l.s.Slf4jSpyLogDelegator: com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)1. /*selectUniqueIdCnt*/SELECT COUNT(0) AS CNTFROM T_UNIQUE_IDWHERE UNIQUE_ID = 'not-unique-id'AND USE_YN = 'Y'{executed in 1 msec}2026-02-14 13:56:38,597 [INFO ] [http-nio-auto-1-exec-3] n.s.l.l.s.Slf4jSpyLogDelegator:|----||cnt ||----||0 ||----|2026-02-14 13:56:38,604 [ERROR] [http-nio-auto-1-exec-3] b.m.c.c.e.a.RestApiExceptionAdvice: ### MethodArgumentNotValidException occurredorg.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<biz.mintchoco.carrot.common.dto.ResponseDto<kotlin.Unit>> biz.mintchoco.carrot.api.spring_boot.controller.SpringBootController.testCustomValidationWithDatabase(biz.mintchoco.carrot.api.spring_boot.dto.CustomValidationWithDatabaseRequestDto): [Field error in object 'customValidationWithDatabaseRequestDto' on field 'uniqueId': rejected value [not-unique-id]; codes [ActiveUniqueId.customValidationWithDatabaseRequestDto.uniqueId,ActiveUniqueId.uniqueId,ActiveUniqueId.java.lang.String,ActiveUniqueId]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [customValidationWithDatabaseRequestDto.uniqueId,uniqueId]; arguments []; default message [uniqueId]]; default message [잘못된 ID입니다.]]...at java.base/java.lang.Thread.run(Thread.java:1583){"payload":null,"isSuccess":false,"resultCode":"WRONG_PARAMETER","message":"잘못된 ID입니다. (uniqueId)"}