ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Controller에서 List 받기(+ dto로 받고 validation)
    Spring Framework 2023. 3. 22. 00:01

    Controller에서 List <String> 받기

    Controller 코드 구성

    import org.springframework.web.bind.annotation.GetMapping
    import org.springframework.web.bind.annotation.RequestParam
    import org.springframework.web.bind.annotation.RestController
    
    @RestController
    class TestController {
    
        @GetMapping("/test/list")
        fun inputList(
            @RequestParam("list") list: List<String>
        ): String {
            println(list)
            return list.toString()
        }
    }

    Postman으로 요청하기

    GET localhost:9090/test/list?list=1,2,3,4,5

    Curl으로 요청하기

    curl --location --request GET 'localhost:9090/test/list?list=1,2,3,4,5'

    요청 결과

    [1, 2, 3, 4, 5]

     

     

    String 대신 직접 만든 객체 사용하기

    WrappingString Data 클래스 만들기

    data class WrappingString(
        val value: String,
    )

     

    Controller String -> WrappingString으로 수정

    @GetMapping("/test/list")
        fun inputList(
            @RequestParam("list") list: List<WrappingString>
        ): String {
            println(list)
            return list.toString()
        }

     

    요청 결과

    [WrappingString(value=1), WrappingString(value=2), WrappingString(value=3), WrappingString(value=4), WrappingString(value=5)]

     

     

    직접 만든 객체 Validtion 하기

    gradle에 Spring validation 의존성 추가

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

     

    WrappingString Data 클래스에 제약조건 추가

     

    data class WrappingString(
        @field:Length(min=2,max=4)
        val value: String,

     

    클래스 단에는 @Validated, 메서드 단에는 @Valid 붙이기

    import com.kidsworld.test.request.WrappingString
    import org.springframework.validation.annotation.Validated
    import org.springframework.web.bind.annotation.GetMapping
    import org.springframework.web.bind.annotation.PathVariable
    import org.springframework.web.bind.annotation.RequestParam
    import org.springframework.web.bind.annotation.RestController
    import javax.validation.Valid
    
    @RestController
    @Validated
    class TestController {
    
        @GetMapping("/test/list")
        fun inputList(
            @Valid @RequestParam("list") list: List<WrappingString>
        ): String {
            println(list)
            return list.toString()
        }
    }

     

    curl 요청

    curl --location --request GET 'localhost:9090/test/list?list=11,22,33,44,5'

     

    요청 결과

    {
        "timestamp": "2023-01-01T16:07:51.753+00:00",
        "status": 500,
        "error": "Internal Server Error",
        "path": "/test/list"
    }

     

    Spring Server Log

    javax.validation.ConstraintViolationException: 
    inputList.list[4].value: 길이가 2에서 4 사이여야 합니다

     

    입력 DTO 객체에 새로운 프로퍼티 추가

    data class WrappingString(
        @field:Length(min=2,max=4)
        val value: String,
    
        @field:NotNull
        val valueLength: String,
    )

     

    이제 다시 요청을 받으면?

    [org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException:
    no matching editors or conversion strategy found]

    매핑 전략을 찾을 수 없다고 나옵니다.

     

     

    음.. @RequestParam을 활용하여 객체를 매핑시키려면 어떻게 해야 할까요?

    @GetMapping("/test/list")
        fun inputList(
            @Valid list: WrappingString
        ): String {
            println(list)
            return list.toString()
        }

    우선 List<DTO> 대신 DTO만 선언하고 @RequestParam 어노테이션을 제거하면 잘 동작합니다.

     

    curl 요청

    curl --location --request GET 'localhost:9090/test/list?value=111&valueLength=3'

     

    요청 결과

    WrappingString(value=111, valueLength=3)

     

    List<DTO>로 받아보기

    @GetMapping("/test/list")
        fun inputList(
            @Valid list: List<WrappingString>
        ): String {
            println(list)
            return list.toString()
        }

     

    curl 요청

    curl --location --request GET 'localhost:9090/test/list?value=111&valueLength=3&value=111&valueLength=3'

     

    요청 결과

    No primary or single unique constructor found for interface java.util.List

    코틀린에서 interfaces는 constructor를 생성할 수 없습니다.

    코틀린의 문제인가 하고 자바에서 동일하게 구현해보니 동일한 에러가 발생합니다.

     

    List대신 ArrayList를 활용

     @GetMapping("/test/list")
        fun inputList(
            @Valid list: ArrayList<WrappingString>
        ): String {
            println(list)
            return list.toString()
        }

    에러는 해결되지만.. 반환값은 []로 옵니다.

    WrappingString에 부 생성자를 두어보았지만 여전히 해결되지 않았습니다.

     

     

    현실과 타협

    여기에는 적지 않았지만 다음과 같은 새로운 에러들을 만나보는 시간들이었습니다.

    Controller The valid characters are defined in RFC 7230 and RFC 3986

     

    이를 해결하기 위한 방안을 생각했습니다.

    • GET요청에 HTTP body 값을 실어서 요청을 보내기(설계적인 문제나 클라이언트에서 지원하지 않을 경우가 있을 수 있음)
    • POST요청에 HTTP body 값을 실어서 요청을 보내기(POST를 사용하는 순간 GET의 의미가 사라지게 됨)

    둘의 장단점을 잘 고려하고 선택해서 사용하면 될 것 같습니다.

     

     

     

     

    참고자료

    https://mangkyu.tistory.com/174

     

    [Spring] @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시 - (1/2)

    Spring으로 개발을 하다 보면 DTO 또는 객체를 검증해야 하는 경우가 있습니다. 이를 별도의 검증 클래스로 만들어 사용할 수 있지만 간단한 검증의 경우에는 JSR 표준을 이용해 간결하게 처리할 수

    mangkyu.tistory.com

    https://stackoverflow.com/questions/33592417/clean-way-to-bind-requestparam-to-an-object-instead-of-a-simple-type

     

    Clean way to bind @RequestParam to an object instead of a simple type

    I want to allow pagination in a REST API I am writing. I would like to send requests as follows GET /people?page[number]=1page[size]=25 or maybe GET /people?page.number=1page.size=25 The request

    stackoverflow.com

     

    댓글

Designed by Tistory.