본문 바로가기

FastCampus - 한번에 끝내는 Java|Spring 웹 개발/03 스프링 입문

스프링 Ch 6-2 custom validation - 패스트캠퍼스 챌린지 9일차

어제에 이어 spring validation 입니다. 오늘 강의 영상은 custom validation 부분인데, 어제 실습도 안 했으니 그것도 겸합니다.

마음같아선 진도를 더 나가고 싶긴 한데, 구정인 것도 있고 해서 일단 강의 하나와 더는 미룰 수 없는 실습 정도로 만족해야겠습니다.

검증은 스펙이다

저번에 언뜻 보고 Hibernate 의 구현체를 사용하는 거까진 봤는데, 알고보니 표준? 이라고 해야하나? 공통 스펙이 있고 그걸 구현하는 형태인 모양이더라구요. 유독 자바가 이런 게 많네요? 험난한 길을 먼저 앞서서 달렸다보니 이런거려나요.

hibernate 의 구현체는 spring boot starter validationm 에 구현체로서 포함되어있다고 합니다. 어노테이션 자동완성을 켜면 꼭 두 개가 나오던데 hibernate 쪽의 어노테이션은 deprecated 처리가 되어있더라구요. 표준화되었기 때문이라고 봐야겠죠...?

참고: https://meetup.toast.com/posts/223 (오버뷰용으로도 좋음, 중복 검증이 성능을 저하시킬 수 있다는 걸 강조하는 게 인상적)

Kotlin 에서 data class 에 validation 을 달려면 @field:Anno 로

저번 강의에서 실습을 안 해봤어서 몰랐습니다.

kotlin 에서 DTO 라면 거의 순수 데이터 같은 객체니까 data class지! 라고 생각해서 생성자이자 필드에 어노테이션을 달면 생성자에 어노테이션을 단 걸로 취급됩니다. 그래서 @field:어노테이션 형태로 달아야 합니다.

import javax.validation.constraints.Email
import javax.validation.constraints.Size

data class UserData(
    @field:Size(min=5)
    var name: String,
    @field:Email
    var email: String,
    @field:PineapplePizza
    var favourite: String?,
)

많은 분들이 이 문제를 겪으셨는지 금방 검색에 나오더라구요. 참고:

Custom Validation

커스텀 검증은 두 가지 방법을 사용할 수 있습니다.

  • @AssertTrue / @AssertFalse - 같은 클래스에 메서드를 하나 만들어 검증합니다.
  • 커스텀 어트리뷰트 만들기 - 어트리뷰트를 만들고, 실제 검증 로직이 들어간 클래스를 만든 뒤 두 개를 이어줘야 합니다.

Custom Validation - @AssertTrue

강의 중에 메서드 이름에 is가 빠져서 안 되는 일이 있었습니다. 저는 여기서 더 나아가 아예 is 빼고 나머지를 필드명과 무관하게 만들어봤는데, 되더라구요. is만 있으면 됩니다. (사실 이 부분을 좀 더 검색해봐야 하는데... 해봐야하는데...)

class UserData {
    @AssertTrue
    fun isMyCustom(): Boolean = false
}

Custom Validation - 커스텀 어트리뷰트 만들기

어트리뷰트와 로직 클래스 두 개를 짜야합니다.

Custom Validation - 커스텀 어트리뷰트 만들기 - 어트리뷰트 클래스

첫 번째로 어트리뷰트를 만들어야 합니다. - @Constraint( validatedBy = { 아래에서_만든_클래스.javaClass } )

kotlin 이라면 만드는 방법이 조금 다릅니다. 언어 차원에서 좀 예쁘게 만들게 해주려나보네요.

  • 클래스는 KClass 로 바꿔써야 합니다. (컴파일 될 때 바꿔준다고 합니다.)
  • 각 패러미터는 전부 있어야 정상적으로 동작합니다. (ConstraintDefinitionException)
    • PineapplePizza contains Constraint annotation, but does not contain a message parameter.
    • PineapplePizza contains Constraint annotation, but does not contain a groups parameter.
    • PineapplePizza contains Constraint annotation, but does not contain a payload parameter.
import javax.validation.Constraint
import javax.validation.Payload
import kotlin.reflect.KClass

@Constraint( validatedBy = [PineapplePizzaValidator::class] )
annotation class PineapplePizza(
    val awesomeThingIs: String = "pineapple pizza",

//    val message: String = "{javax.validation.constraints.Email.message}",
    val message: String = "파인애플 피자가 최고의 음식입니다.",
    val groups: Array<KClass<*>> = [],
    val payload: Array<KClass<out Payload>> = [],
)

두 번째로, 어트리뷰트에서 호출할 검증 메서드가 있는 클래스를 만들어야 합니다.

import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext

class PineapplePizzaValidator: ConstraintValidator<PineapplePizza, String> {
    lateinit var definedAwesomeThing: String

    override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {
        return value?.lowercase() == "pineapple pizza"
    }

    override fun initialize(constraintAnnotation: PineapplePizza) {
        // take value from anno
        super.initialize(constraintAnnotation)
        definedAwesomeThing = constraintAnnotation.awesomeThingIs
    }
}

검증 패러미터와 http 반환값

저번 강의에서 http 리턴값이 뭐가 나오는지 확실히 보지 못했었습니다. 직접 해보니, 반환값에 관계없이 패러미터에 bindingResult 가 있으면 본문이 실행되고 그렇지 않으면 검증 실패시 함수 본문은 실행되지 않고 대신 400을 떨굽니다.

컬렉션이 필드에 있다면 검증하려면 @Valid 를 붙여라

사소하지만 중요한 내용으로 끝부분에 이게 언급되더라구요.


미뤄둔 실습을 오늘 몰아서 하려니까 양이 좀 많고, 시행착오도 좀 있네요. 원래는 공식 문서를 찾아보고 싶었는데 생각외로 검색결과의 블로그들이 잘 정리되어있어서 거기까지 갈 생각이 안 나더라구요. 이런 습관은 매우 나쁜데... 공식이 설명하는 거하고 블로그에서 설명하는 거하고 확 다른데...

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스: https://bit.ly/37BpXiC

#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는JavaSpring웹개발마스터초격차패키지Online

오늘은 막 집에 돌아온 참이고 해서 정신이 좀 없어서, 마음은 진도를 빼고 싶었지만 딱 강의 하나를 보고 끝내야 했습니다. 내일은 좀 더 짧은 시간에 집중해서 할 수 있도록 일찍 일어나던지 해야...

이...게 강의가 DB 관련까지는 진행이 되어야 뭘 만들어보던지 하는데 커리큘럼을 보니 이 뒤에 swagger 나 예외처리 등의 기초가탄탄 같은 내용들이 있네요. 좋은데 답답한데 좋은데 답답하네요...