개발
home
💍

Kotlin Spring Boot Bean Validation

Created
2022/03/01
Tags
SpringBoot
Kotlin
Bean Validation
createdAt: 2022-03-01 @이영훈 updatedAt: 2024-01-10 @이영훈
코프링(Kotlin Spring Boot)에서 Bean Validation을 이용하여 유효성 검사를 하였습니다.
자바 환경에서와 다르게 코틀린에서는 어노테이션 사용 위치를 지정해주어야 하는 게 가장 큰 특징입니다

Validation 의존성 설정

build.gradle.kts 파일에 spring-boot-starter-validation을 추가합니다
// build.gradle.kts dependencies { // ... implementation("org.springframework.boot:spring-boot-starter-validation") // ... }
Kotlin
복사

@RequestBody 유효성 검사

컨트롤러에서 검사하고 싶은 CreateJobDto 앞에 @Validated 어노테이션을 붙입니다
// JobController.kt import org.springframework.validation.annotation.Validated @PostMapping("/v1/jobs") fun createJob( @RequestBody @Validated createJob: CreateJobDto, ) { // logic... }
Kotlin
복사
그리고 CreateJobDto에 제약조건 어노테이션을 붙입니다
Kotlin에서는 다음 사항을 유의해야 합니다
제약조건 어노테이션을 getter(@get:) 또는 field(@field:)로 명시합니다 (코틀린 공식 문서)
@get: 을 해도 되고 @field: 로 되는데, 아래 코드에서는 @field: 로 하였습니다
// CreateJobDto.kt import jakarta.validation.constraints.FutureOrPresent import jakarta.validation.constraints.Min import jakarta.validation.constraints.NotEmpty class CreateJobDto( // 제목: not null, not blank @field:NotEmpty(message = "title should not be null nor empty") val title: String, // 작업 시작 시간: 현재나 미래만 가능 @field:FutureOrPresent(message = "startAt should not be past") val startAt: OffsetDateTime, // 수당: 500 이상 (같거나 초과) @field:Min(500) val pay: Int, )
Kotlin
복사

@Neested class 유효성 검사

nested class의 유효성 검사에는 @Valid 어노테이션을 붙입니다
이번에도 마찬가지로 getter(@get:)나 field(@field:)에 붙여야 합니다
class CreateJobDto( // 제목: not null, not blank @get:NotEmpty(message = "title should not be null nor empty") val title: String, // 작업 시작 시간: 현재나 미래만 가능 @get:FutureOrPresent(message = "startAt should not be past") val startAt: OffsetDateTime, // 수당: 500 이상 (같거나 초과) @get:Min(500) val pay: Int, // 참여 개발자: 참여 개발자는 null일 수 없습니다 @get:NotNull(message = "developers should not be null") // nested class 유효성 검사 @get:Valid val developers: List<DeveloperDto>, ) class DeveloperDto( @get:NotEmpty(message = "developer's name should not be null nor empty") val name: String, @get:NotEmpty(message = "developer's position should not be null nor empty") val position: String, )
Kotlin
복사

@RequestParam 유효성 검사

Controller 클래스에 @Validated 어노테이션을 붙여줍니다
그리고 @RequestParam의 파라미터에 제약 조건을 선언합니다
예제에는 page가 1부터 시작한다라는 제약 조건을 걸었습니다 (@Min)
@Validated @RestController @RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) class JobController( private val jobService: JobService, ) { @GetMapping("/v1/jobs") fun getJobs( @Min(1) @RequestParam page: Int, ) { // logic... } }
Kotlin
복사

@PathVariable 유효성 검사

@RequestParam 유효성 검사와 동일합니다. Controller 클래스에 @Validated 어노테이션을 선언하고, @PathVariable의 파라미터에 제약 조건을 붙여줍니다.
@Validated @RestController @RequestMapping(produces = [MediaType.APPLICATION_JSON_VALUE]) class JobController( private val jobService: JobService, ) { @GetMapping("/v1/jobs/{jobId}") fun getJob( @Min(10) @PathVariable("jobId") jobId: Long, ) { // logic... } }
Kotlin
복사

@ExceptionHandler로 예외처리하기

1.
@RequestBody의 파라미터가 유효하지 않으면 MethodArgumentNotValidException 에러가 발생합니다
2.
@RequestParam과 @PathVariable의 파라미터가 유효하지 않으면 ConstraintViolationException 에러가 발생합니다
해당 에러 두개를 @RestControllerAdvice와 @ExceptionHandler를 사용하여 처리합니다.
원하는 내용으로 적절히 커스텀하여 사용합니다. 저는 기존에 정의한 내용을 토대로 수정하여 사용하였습니다. (관련 포스팅)
@RestControllerAdvice class ExceptionControllerAdvice { // @RequestBody의 파라미터가 유효하지 않으면 MethodArgumentNotValidException 에러가 발생합니다 @ExceptionHandler fun handleInvalidRequestBodyException(e: MethodArgumentNotValidException): ResponseEntity<ExceptionResult> { val allErrors = e.bindingResult.allErrors.map { it.defaultMessage } val statusCode = HttpStatus.BAD_REQUEST val exceptionResult = ExceptionResult(statusCode.value(), allErrors, null) return ResponseEntity(exceptionResult, statusCode) } // @RequestParam과 @PathVariable의 파라미터가 유효하지 않으면 ConstraintViolationException 에러가 발생합니다 @ExceptionHandler fun handleConstraintViolationException(e: ConstraintViolationException): ResponseEntity<ExceptionResult> { val statusCode = HttpStatus.BAD_REQUEST val exceptionResult = ExceptionResult(statusCode.value(), e.message!!, null) return ResponseEntity(exceptionResult, statusCode) } }
Kotlin
복사

@RequestBody 유효성 검사

Ref.