미해결사건. @Valid가 작동하지 않는다??
728x90
반응형

서두

본 글은 코틀린 + 스프링부트 환경에서 @Valid가 동작하지 않는 케이스를 다루었고 해당 이슈를 완전히 해결하지 못했음. 해결을 하기는 했는데 해결이 된 이유를 알 수 없음. 완벽한 해결책을 찾고자 했다면 "뒤로가기" 버튼을 누르기 바람

 

 

설명에 앞서 개발환경을 간단히 소개하고자 한다. 혹시나 나와 비슷한 상황을 겪고 이를 해결한 분이 계시다면 댓글로 안내를 부탁드...립니다. (제발) 아래는 gradle 스크립트 중 일부이다.

plugins {
  id("org.springframework.boot") version "2.5.0"
  id("io.spring.dependency-management") version "1.0.11.RELEASE"
  kotlin("jvm") version "1.5.10"
  kotlin("plugin.spring") version "1.5.10"
  ...

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-validation")
  ...

 

🤯 이슈 한 줄 요약

@Valid를 사용했으나 값 검증을 하지 않는다.

 

 

💡 해결 한 줄 요약

@Valid를 사용할 때, @Validated도 함께 사용한다.

 

 

이제 구구절절 설명을 해보자면... 지금 다루려는 @Valid는 자바 표준 검증 어노테이션으로 JSR-303이란 이름으로 채택된 서블릿 2.3 표준스펙 중 유효성 검증을 위한 어노테이션이다. 그리고 또 하나의 유효성 검증을 하기 위해 사용하는 어노테이션인 @Validated는 스프링에서 제공해주고 있다. 

 

@Valid를 코틀린 + 스프링 환경에서 아래와 같이 사용하고 있다.

import io.swagger.annotations.Api
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import org.springframework.data.web.PageableDefault
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

@Api(description = "서적 관리")
@RestController
@RequestMapping("sample")
class MyController(
    val myService: MyService,
) {

    @Operation(summary = "서적 등록")
    @PostMapping
    fun createBook(@RequestBody @Valid list: List<BookCreateModel>) {
        myService.createBook(list)
    }
    ...
}
import io.swagger.annotations.ApiModelProperty
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull

data class MyCreateModel(

    @ApiModelProperty(value = "소유자 ID", example = "1")
    @field:NotNull(message = "소유자 ID를 입력하세요")
    val userId: Long? = null,

    @ApiModelProperty(value = "서적 코드", example = "SAMPLE00001")
    @field:NotBlank(message = "서적 코드를 입력하세요")
    val bookCode: String? = null,

    ...
) {
  ...
}

 

 

위 @Valid에서 실패가 검증되도록 API를 호출해보자

// POST http://localhost:8088/mybook/sample

[
    {
        "userId" : 1
    }
]

 

 

위와 같이 실행했을 때, 필자가 기대한 것은 에러 메시지로 "서적 코드를 입력하세요"가 나타나는 것이었다. 하지만 검증이 Pass가 되고 서비스단에서 필요한 값이 없어서 NullPointerException이 나타났다. 즉 @Valid가 동작하지 않았다. 어떻게 된 일일까?

 

@Valid가 동작하지 않는 케이스를 살펴보았다. 아래 포스팅에서 해당 케이스를 잘 정리하고 있다. 자, 이제 필자의 케이스와 비교해보자.

 

[스프링][코틀린] validation이 안될 때 확인해볼 것

Kotlin으로 @Valid를 사용하려고 하니 잘 안됐습니다. 몇 가지 확인해볼 것을 아래에 정리합니다. 1. data class의 생성자에 제약 조건을 걸지 않았는지 체크 //잘못된 코드 data class UserInfoDto ( @NotEmpty(m..

darkstart.tistory.com

 

🧐 필자의 케이스

 

data class의 생성자에 제약 조건을 걸지 않았는지 체크

 

제대로 @field에 입력하고 있다. 해당없음

 

 

적절한 곳에 @Valid 사용했는지 체크

RequestBody와 함께 매개변수 받는 쪽에서 사용했다. 해당없음

 

 

<arg>-Xemit-jvm-type-annotations</arg>

메이븐이라서 그래들에 맞게 테스트를 했으나 상황은 같았다. 또한 코틀린 버전이 낮지도 않고 (1.5.10) 더 올려서(1.6.10) 테스트 해 보아도 상황이 같다.

// build.gradle.kts
tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict", "-Xemit-jvm-type-annotations")
        jvmTarget = "11"
    }
}

 

 

dependency 체크

위에 공유한 블로그에서는 jakarta를 추가하지 않았는지 확인해보라고 하지만, 내 프로젝트에서는 jakarta가 존재하지 않으며 spring-boot-starter-validation도 제대로 추가되어 있다.

 

 

그러면 어떻게 해야 원하는대로 동작할 것인가? 바로 Validated 어노테이션을 추가하면 된다. 필자가 알기로는 Validated와 Valid, 둘 중 하나만 사용해도 유효성 검증이 가능한데 코틀린 + 스프링 환경에서는 이 둘 모두를 사용해야 한다. 문제는 "왜"인지 알 수 없다는 것. 둘 모두를 사용해야 유효성 검증을 할 수 있다는 것은 알겠는데 아무리 찾아봐도 이유를 알 수 없었다.

...
import org.springframework.validation.annotation.Validated

@Api(description = "서적 관리")
@Validated
@RestController
@RequestMapping("sample")
class MyController(
    val myService: MyService,
) {

    @Operation(summary = "서적 등록")
    @PostMapping
    fun createBook(@RequestBody @Valid list: List<BookCreateModel>) {
        myService.createBook(list)
    }
    ...
}

처음에는 위쪽 이미지, 지금은 아래쪽 이미지의 상황. 하...

 

 

이 방법말고 모델 클래스를 Java로 만드는 방법도 있다. 원하는 형태로 진행하면 된다.

 

[Spring] 스프링 - Form 유효성 체크 with kotlin #3

본 예제는 Spring 5.x & Spring boot 2.3.0 버전을 사용합니다. 또한 Kotlin (v1.3.72)로 예제를 작성하며 IntelliJ CE를 사용합니다. 스프링에서는 form에 대한 유효성 체크를 간단하게 할수 있도록 도와주는 lib..

tourspace.tistory.com

 

이 사항에 대해 제대로 원인/이유 등을 아는 분이라면 댓글로 꼭! 꼭! 꼭! 알려주길 간절히 부탁...드립니다.

 

728x90
반응형