Kotlin+spring boot에서 MyBatis Collection ResultMap

조갱·2022년 8월 15일
2

kotlin

목록 보기
10/10

실무에서

kotlin+spring boot를 사용중인데, MyBatis에서 Collection ResultMap을 구성하며 겪었던 삽질? 에 대해 공유하고자 한다.

바쁜 사람들을 위해

결론부터 말하자면, 모든 필드에 기본값을 정의해야 한다. (기본 생성자 : default constructor 가 존재해야 하므로)

초기 Data Class

커머스 분야인 우리는, 데이터 구조를 크게
주문번호 : 상품번호 = 1:n
상품번호 : 옵션번호 = 1:n 으로 관리하고 있다.

당연하게도, 1개의 주문은 최소 1개의 상품번호와 최소 1개의 옵션번호를 가진다.
그래서 모든 필드는 NotNull이다.

@Alias("elasticSearchStatisticSyncSearchView")
data class ElasticSearchStatisticSyncSearchView(
    val orderNo: String,
    val registerType: String,
    val orderProduct: List<ElasticSearchOrderProduct>,
)

data class ElasticSearchOrderProduct(
    val orderNo: String,
    val orderProductNo: String,
    val registerType: String,
    val orderProductOption: List<ElasticSearchOrderProductOption>,
)

data class ElasticSearchOrderProductOption(
    val orderNo: String,
    val orderProductNo: String,
    val orderProductOptionNo: String,
    val registerType: String,
)

초기 MyBatis resultMap

    <resultMap id="elasticSearchStatisticSyncSearchView" type="ElasticSearchStatisticSyncSearchView">
        <id property="orderNo" column="so_order_no"/>
        <result property="registerType" column="so_register_type"/>
        <collection property="orderProduct" resultMap="stOrderProduct" javaType="java.util.List"/>
    </resultMap>

    <resultMap id="stOrderProduct" type="ElasticSearchOrderProduct">
        <id property="orderProductNo" column="sop_order_product_no"/>
        <result property="orderNo" column="sop_order_no"/>
        <result property="registerType" column="sop_register_type"/>
        <collection property="orderProductOption" resultMap="stOrderProductOption"
                    javaType="java.util.List"/>
    </resultMap>

    <resultMap id="stOrderProductOption" type="ElasticSearchOrderProductOption">
        <id property="orderProductOptionNo" column="sopo_order_product_option_no"/>
        <result property="orderNo" column="sopo_order_no"/>
        <result property="orderProductNo" column="sopo_order_product_no"/>
        <result property="registerType" column="sopo_register_type"/>
    </resultMap>

실행해보자!

그렇게 db에서 데이터를 긁어오는 API를 실행해봤는데,,,

500 에러

HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Length: 121

{
  "path": "/... {비밀}",
  "status": 500,
  "code": "E0000",
  "message": "... {비밀}"
}

500 에러가 발생했다.

서버 로그

를 확인해보니

Caused by: org.apache.ibatis.reflection.ReflectionException: Error instantiating class
com.ncp.order.core.domain.statistic.model.ElasticSearchStatisticSyncSearchView with invalid types (String,String,List) or values ({orderNo},{registerType},{orderProductNo}).
Cause: java.lang.IllegalArgumentException: argument type mismatch

타입이 맞지 않는단다.
게다가 ElasticSearchStatisticSyncSearchView의 타입은 (String, String, List) 인데 실제로는 orderNo, registerType, orderProductNo가 들어온게 이상하다.

역시 stack overflow

해당 오류메시지 (Mybatis invalid types) 를 검색했더니 스택 오버플로우의 게시글을 볼 수 있었다. : https://stackoverflow.com/questions/16774448/mybatis-and-invalid-types

답변 내용은 간단했다. You need to have an empty constructor

kotlin의 data class

kotlin 코드를 java코드로 변환해보면, data class의 생성자가 java 코드로 어떻게 변환되는지 알 수 있다. : kotlin 코드를 java로 변환

data class ElasticSearchStatisticSyncSearchView(
    val orderNo: String,
    val registerType: String,
    val orderProduct: List<ElasticSearchOrderProduct>,
)

위와 같이 아무런 초기값이 없는 경우 java 코드로는

모든 필드를 받는 1개의 생성자만 생성이 된다.

data class ElasticSearchStatisticSyncSearchView(
    val orderNo: String = "", // 1개의 기본값
    val registerType: String,
    val orderProduct: List<ElasticSearchOrderProduct>,
)

기본값이 1개가 있는 경우는

생성자가 오버로딩이 된다.

data class ElasticSearchStatisticSyncSearchView(
    val orderNo: String = "",
    val registerType: String = "",
    val orderProduct: List<ElasticSearchOrderProduct>? = null,
)

모든 필드에 기본값이 있는 경우는

기본 생성자가 오버로딩된다.

아무튼, 아까 stack overflow에서 기본 생성자가 존재해야한다고 했기 때문에 모든 필드에 기본값을 넣어본다.

수정된 data class

@Alias("elasticSearchStatisticSyncSearchView")
data class ElasticSearchStatisticSyncSearchView(
    val orderNo: String = "",
    val registerType: String = "",
    val orderProduct: List<ElasticSearchOrderProduct>? = null,
)

data class ElasticSearchOrderProduct(
    val orderNo: String = "",
    val orderProductNo: String = "",
    val registerType: String = "",
    val orderProductOption: List<ElasticSearchOrderProductOption>? = null,
)

data class ElasticSearchOrderProductOption(
    val orderNo: String = "",
    val orderProductNo: String = "",
    val orderProductOptionNo: String = "",
    val registerType: String = "",
)

기본값만 추가가 됐다. 그리고 다시 돌려보면

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 450811

[
  {
    "orderNo": "{주문번호}",
    "registerType": "{타입}",
    "orderProduct": [
      {
        "orderNo": "{주문번호}",
        "orderProductNo": "{상품번호1}",
        "registerType": "{타입}",
        "orderProductOption": [
          {
            "orderNo": "{주문번호}",
            "orderProductNo": "{상품번호1}",
            "orderProductOptionNo": "{옵션번호1}",
            "registerType": "{타입}"
          },
          {
            "orderNo": "{주문번호}",
            "orderProductNo": "{상품번호1}",
            "orderProductOptionNo": "{옵션번호2}",
            "registerType": "{타입}"
          }
        ]
      },
      {
        "orderNo": "{주문번호}",
        "orderProductNo": "{상품번호2}",
        "registerType": "{타입}",
        "orderProductOption": [
          {
            "orderNo": "{주문번호}",
            "orderProductNo": "{상품번호1}",
            "orderProductOptionNo": "{옵션번호3}",
            "registerType": "{타입}"
          }
        ]
      }
    ]
  }
  ... 중략

과 같이 잘 출력된다.

Reference
https://stackoverflow.com/questions/16774448/mybatis-and-invalid-types

profile
A fast learner.

0개의 댓글