Skip to content

Custom validation with database

데이터베이스의 값을 사용해서 커스텀 유효성 검사

  1. 어노테이션 생성 및 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>> = [],
    )
    @Component
    class 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
    }
    }
  2. 요청 DTO 클래스의 멤버에 제약 설정
    any request dto class
    @field:Schema(description = "ID")
    @field:NotBlank
    @field:ActiveUniqueId
    var uniqueId: String,
  3. 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)))
    }
  4. Validation 오류가 발생하면 MethodArgumentNotValidException 예외가 던져지는데, 이 예외가 발생시 405 Bad Request 오류가 발생하도록 설정
    RestApiExceptionAdvice.kt
    @ExceptionHandler(MethodArgumentNotValidException::class)
    @ResponseBody
    fun 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)))
    }
  5. 테스트
    any test class
    @DisplayName("커스텀 유효성 체크 with 데이터베이스 테스트")
    @Test
    fun `커스텀 유효성 체크 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())
    }
    }
  6. 로그 확인
    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 CNT
    FROM T_UNIQUE_ID
    WHERE 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 occurred
    org.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)"}