[Database Rider] JSON 타입 사용하기 (DbUnit)

by 스뎅(thDeng) on

문제

MySQL에서 JSON 타입이나 GEOMETRY 타입으로 되어 있는 column을 Database Rider(또는 DbUnit)로 사용하려면 다음과 같은 오류가 발생한다.

2021-03-09 18:40:51.980  WARN 9317 --- [   SpecRunner-1] o.dbunit.dataset.AbstractTableMetaData   : Potential problem found: The configured data type factory 'class org.dbunit.dataset.datatype.DefaultDataTypeFactory' might cause problems with the current database 'MySQL' (e.g. some datatypes may not be supported properly). In rare cases you might see this message because the list of supported database products is incomplete (list=[derby]). If so please request a java-class update via the forums.If you are using your own IDataTypeFactory extending DefaultDataTypeFactory, ensure that you override getValidDbProducts() to specify the supported database products.
2021-03-09 18:40:51.980  WARN 9317 --- [   SpecRunner-1] org.dbunit.util.SQLHelper                : ad_targeting.conditions data type (1111, 'JSON') not recognized and will be ignored. See FAQ for more information.
2021-03-09 18:40:51.985  WARN 9317 --- [   SpecRunner-1] o.s.test.context.TestContextManager      : Caught exception while invoking 'beforeTestMethod' callback on TestExecutionListener [com.github.database.rider.spring.DBRiderTestExecutionListener@107fbca8] for test method [public java.lang.String kr.leocat.test.service.domain.adtargeting.repository.OrderRepositoryIntegrationTest$ByteBuddy$rkxW0aqO.save()] and test instance [kr.leocat.test.service.domain.adtargeting.repository.OrderRepositoryIntegrationTest@3886bfec]

java.lang.RuntimeException: Could not create dataset for test 'save'.

  ... (생략) ...

Caused by: org.dbunit.dataset.NoSuchColumnException: ad_targeting.CONDITIONS -  (Non-uppercase input column: conditions) in ColumnNameToIndexes cache map. Note that the map's column names are NOT case sensitive.
	at org.dbunit.dataset.AbstractTableMetaData.getColumnIndex(AbstractTableMetaData.java:117) ~[dbunit-2.7.0.jar:na]
	at org.dbunit.operation.AbstractOperation.getOperationMetaData(AbstractOperation.java:89) ~[dbunit-2.7.0.jar:na]
	at org.dbunit.operation.AbstractBatchOperation.execute(AbstractBatchOperation.java:151) ~[dbunit-2.7.0.jar:na]
	at org.dbunit.operation.CompositeOperation.execute(CompositeOperation.java:79) ~[dbunit-2.7.0.jar:na]
	at com.github.database.rider.core.dataset.DataSetExecutorImpl.createDataSet(DataSetExecutorImpl.java:163) ~[rider-core-1.23.0.jar:na]
	... 31 common frames omitted

원인 및 해결방법

DbUnit은 JSON이라는 타입을 몰라서 발생하는 문제이고, DataTypeFactory 설정을 해주면 된다. GEOMETRY 타입도 마찬가지이다.

(spock 에서 사용했던 groovy 코드)

package kr.leocat.test.dbunit

import org.dbunit.dataset.datatype.DataType
import org.dbunit.dataset.datatype.DataTypeException
import org.dbunit.ext.mysql.MySqlDataTypeFactory
import org.slf4j.Logger
import org.slf4j.LoggerFactory

class CustomMySqlDataTypeFactory extends MySqlDataTypeFactory {

    Logger logger = LoggerFactory.getLogger(CustomMySqlDataTypeFactory.class)

    @Override
    DataType createDataType(int sqlType, String sqlTypeName) throws DataTypeException {

        logger.debug("createDataType(sqlType={}, sqlTypeName={}) - start", String.valueOf(sqlType), sqlTypeName)

        if ("JSON".equalsIgnoreCase(sqlTypeName)) {
            return DataType.CLOB
        }

        if ("GEOMETRY".equalsIgnoreCase(sqlTypeName)) {
            return DataType.BINARY
        }

        return super.createDataType(sqlType, sqlTypeName)
    }
}

아래는 동일한 내용의 Kotest 용 Kotlin 코드이다.

package kr.leocat.test.dbunit

import org.dbunit.dataset.datatype.DataType
import org.dbunit.ext.mysql.MySqlDataTypeFactory
import org.slf4j.LoggerFactory

/**
 * DB Unit의 MySqlDataTypeFactory에는 JSON 타입이 존재하지 않아서 JSON데이터를 입력할때 에러가 발생한다.
 * 따라서, JSON 타입의 경우에는 CLOB으로 매핑하여 에러 해결.
 *
 * **NOTE:** 지우지 말 것!! `dbunit.yml` 파일에서 사용 중!!
 */
class CustomMySqlDataTypeFactory : MySqlDataTypeFactory() {

    override fun createDataType(sqlType: Int, sqlTypeName: String?): DataType {
        log.debug("createDataType(sqlType=$sqlType, sqlTypeName=$sqlTypeName) - start")

        return when {
            sqlTypeName.equals("JSON", ignoreCase = true) -> DataType.CLOB
            sqlTypeName.equals("GEOMETRY", ignoreCase = true) -> DataType.BINARY
            else -> super.createDataType(sqlType, sqlTypeName)
        }
    }

    companion object {
        private val log = LoggerFactory.getLogger(CustomMySqlDataTypeFactory::class.java)
    }
}

이 DataTypeFactory 를 DbUnit 설정에 추가해 주면 끗.

cacheConnection: true
cacheTableNames: true
leakHunter: false
caseInsensitiveStrategy: !!com.github.database.rider.core.api.configuration.Orthography 'LOWERCASE'
properties:
  datatypeFactory: !!kr.leocat.test.dbunit.CustomMySqlDataTypeFactory { }
	...

참고

별도로 명시하지 않을 경우, 이 블로그의 포스트는 다음 라이선스에 따라 사용할 수 있습니다: Creative Commons License CC Attribution-NonCommercial-ShareAlike 4.0 International License