관리 메뉴

피터의 개발이야기

[Kotlin] Spring Validation 이용한 입력 데이터 유효성 검증 본문

Programming/Kotlin

[Kotlin] Spring Validation 이용한 입력 데이터 유효성 검증

기록하는 백앤드개발자 2024. 7. 30. 10:10
반응형

ㅁ 들어가며

ㅇ 입력값에 대한 유효성 검증을 코드상에서 하나하나 처리하는 것은 번거롭다. 

ㅇ Spring에서 제공하는 Validation 기능을 사용하면 Controller 단과 별개로 입력값을 간편하게 검증할 수 있다. 

ㅇ 이 글에서는 Spring Validation을 Kotlin에서 사용하는 방법을 정리한다.

ㅇ Kotlin, Spring Validation 이용한 입력 데이터 유효성 검증의 글을 보고 실습을 수행하였다.

 

ㅁ Gradle 설정

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

ㅇ Spring Boot 2.3 버전부터 spring-boot-starter-validation 의존성을 명시해야 @Valid를 사용할 수 있다.

 

ㅁ Valid 사용

import jakarta.validation.constraints.NotNull
import java.time.LocalDate

data class ValidRequestDto(
    @field:NotNull(message = "value는 필수 입력값입니다.")
    val value: String? = null,

    @field:NotNull(message = "createdAt은 필수 입력값입니다.")
    val createdAt: LocalDate? = null,

    @field:NotNull(message = "number는 필수 입력값입니다.")
    val number: Long? = null
)

ㅇ Kotlin에서는 @field:를 prefix로 붙여야 한다. 그렇지 않으면 Java로 변환 시 constructor에 Valid 어노테이션이 붙게 된다.

 

ㅁ 클래스 멤버 필드는 nullable로 지정

data class ValidRequestDto(
    @field:NotNull(message = "value는 필수 입력값입니다.")
    val value: String
)

ㅇ Kotlin에서는 변수 선언 시 nullable한 타입으로 선언해야 한다.

ㅇ NotNull을 적용할 때 nullable 타입으로 선언하지 않으면, 요청 시 해당 필드를 입력하지 않았을 때 NullPointerException이 발생하게 된다.

Constructor threw exception; nested exception is java.lang.NullPointerException; Parameter specified as non-null is null

 

ㅁ Controller 단에 Validation 적용

@RestController
@RequestMapping("/valid")
class ValidController() {
	@GetMapping("/get")
	fun getParam(
		@Valid @ModelAttribute validRequestDto: ValidRequestDto,
		bindingResult: BindingResult
	) {
		if (bindingResult.hasErrors()) {
			println("errors : ${bindingResult.fieldErrors}")
			throw RuntimeException("errors ${bindingResult.fieldErrors}")
		}
	}
}

ㅇ BindingResult는 @Valid가 적용된 @ModelAttribute, @RequestBody 바로 뒤에 argument로 설정해야 한다.

ㅇ @Valid와 BindingResult 사이 다른 인자가 존재하면 에러 발생 시 BindingResult에 에러메시지가 담기지 않는다.

 

ㅁ Controller 내 여러 Validation 형태

ㅇ Validation 적용 방식에 따라 미묘한 차이가 발생한다.

 

@ModelAttribute

@GetMapping("/get")
fun getParam(
    @Valid @ModelAttribute validRequestDto: ValidRequestDto
) {
    // ...
}

ㅇ 유효성 검증 에러가 발생하면 기본적으로 400 응답 코드를 반환하며, BindException이 발생한다.

 

@RequestBody

@PostMapping("/post")
fun postRequestBody(
    @Valid @RequestBody validRequestDto: ValidRequestDto
) {
    // ...
}

ㅇ 유효성 검증 에러가 발생하면 400 응답 코드를 반환하며, MethodArgumentNotValidException이 발생한다.

 

@RequestParam, @PathVariable

@RestController
@Validated
class ValidationController {
    @GetMapping("/get")
    fun getParam(
        @NotNull(message = "필수값입니다.") @RequestParam("value") value: String?
    ): String? {
        // ...
    }
}

ㅇ @RequestParam은 클래스 단계에서 @Validated 어노테이션이 필요하다. 

ㅇ 기본적으로 500 응답 코드를 반환하며, ConstraintViolationException이 발생한다.

 

ㅁ Exception Handler

@ExceptionHandler(BindException::class)
fun handleNotValidException(
    e: BindException,
    bindingResult: BindingResult
): ResponseEntity<ErrorDto> {
    logger.error("validation errors : ${bindingResult.fieldErrors}")
    val defaultMessage = bindingResult.fieldError?.defaultMessage
    val code = bindingResult.fieldError?.code
    return ResponseEntity.badRequest()
        .body(
            ErrorDto(
                message = defaultMessage,
                validCode = code,
                description = e.message
            )
        )
}

ㅇ BindingResult, MethodArgumentNotValidException에 대한 exception handler에서 BindingResult를 직접 받아올 수 있다.

 

ㅁ 테스트

@WebMvcTest
class ValidControllersTests(@Autowired val mockMvc: MockMvc) {

    @Test
    fun `get ModelAttribute`(){
        mockMvc.perform(
            get("/valid")
                .accept(MediaType.APPLICATION_JSON)
                .param("createdAt", LocalDate.now().toString())
                .param("number", "10")
               // .param("value", "value")
        )
            .andExpect(status().isOk)
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
    }
}
/** 출력
errors : [Field error in object 'validRequestDto' on field 'value': rejected value [null]; codes [NotNull.validRequestDto.value,NotNull.value,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validRequestDto.value,value]; arguments []; default message [value]]; default message [value는 필수 입력값입니다.]]
 */

 

ㅁ 마무리

Spring Validation을 사용하면 입력값에 대한 유효성 검증을 간편하게 처리할 수 있다. 

Kotlin에서는 @field: 어노테이션을 사용하고, nullable 타입으로 선언해야 한다. 

Controller 단에서 BindingResult를 사용하여 유효성 검증 에러를 처리할 수 있다.

 

ㅁ 함께 보면 좋은 사이트

ㅇ base source 참조 - https://spring.io/guides/tutorials/spring-boot-kotlin

Kotlin, Spring Validation 이용한 입력 데이터 유효성 검증

반응형
Comments