SpringBoot+Kotlin+Postgres and JSONB: “org.hibernate.MappingException: No Dialect mapping for JDBC type”

后端 未结 2 1181
南旧
南旧 2020-12-19 20:47

I have been consulting a number of approaches/posts/stackoverflow questions in order to deal with the following error (full stack trace) when running a Kotlin/SpringBoot app

2条回答
  •  抹茶落季
    2020-12-19 21:32

    I propose my solution in a pull-request

    The idea is to change Entity to:

    import com.example.demo.pojo.SamplePojo
    import com.vladmihalcea.hibernate.type.json.JsonBinaryType
    import com.vladmihalcea.hibernate.type.json.JsonStringType
    import org.hibernate.annotations.Type
    import org.hibernate.annotations.TypeDef
    import org.hibernate.annotations.TypeDefs
    import javax.persistence.*
    
    @Entity
    @Table(name = "tests")
    @TypeDefs(
            TypeDef(name = "json", typeClass = JsonStringType::class),
            TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)
    )
    data class SampleEntity (
        @Id @GeneratedValue
        val id: Long?,
        val name: String?,
    
        @Type(type = "jsonb")
        @Column(columnDefinition = "jsonb")
        var data: Map?
    ) {
    
        /**
         * Dependently on use-case this can be done differently:
         * https://stackoverflow.com/questions/37873995/how-to-create-empty-constructor-for-data-class-in-kotlin-android
         */
        constructor(): this(null, null, null)
    }
    
    1. Each entity should either have a default constructor or have defaults for all its parameters
    2. Instead of saving POJO, save as Map type

    Since we have a full-control what will be in POJO in business logic the only missing piece will be to convert POJO to Map and Map to POJO

    SamplePojo implementation

    data class SamplePojo(
            val payload: String,
            val flag: Boolean
    )  {
        constructor(map: Map) : this(map["payload"] as String, map["flag"] as Boolean)
    
        fun toMap() : Map {
            return mapOf("payload" to payload, "flag" to flag)
        }
    }
    

    This is rather a workaround but it allows us to work with any depth-level structures.

    P.S. I noticed that you use Serializer and redefined equals, toString, hashCode. You don't need this if using data class.

    UPDATE:

    If you need a more flexible structure than Map, you can use JsonNode. Code example

    Entity:

    import com.fasterxml.jackson.databind.JsonNode
    import com.vladmihalcea.hibernate.type.json.JsonBinaryType
    import com.vladmihalcea.hibernate.type.json.JsonStringType
    import org.hibernate.annotations.Type
    import org.hibernate.annotations.TypeDef
    import org.hibernate.annotations.TypeDefs
    import javax.persistence.*
    
    @Entity
    @Table(name = "tests")
    @TypeDefs(
            TypeDef(name = "json", typeClass = JsonStringType::class),
            TypeDef(name = "jsonb", typeClass = JsonBinaryType::class)
    )
    data class SampleJsonNodeEntity (
            @Id @GeneratedValue
            val id: Long?,
            val name: String?,
    
            @Type(type = "jsonb")
            @Column(columnDefinition = "jsonb")
            var data: JsonNode?
    ) {
    
        /**
         * Dependently on use-case this can be done differently:
         * https://stackoverflow.com/questions/37873995/how-to-create-empty-constructor-for-data-class-in-kotlin-android
         */
        constructor(): this(null, null, null)
    }
    

    Change Entity in Repository:

    import com.example.demo.entity.SampleJsonNodeEntity
    import org.springframework.data.jpa.repository.JpaRepository
    
    interface SampleJsonNodeRepository: JpaRepository {
    }
    

    Tests for both approaches:

    import com.example.demo.DbTestInitializer
    import com.example.demo.entity.SampleJsonNodeEntity
    import com.example.demo.entity.SampleMapEntity
    import com.example.demo.pojo.SamplePojo
    import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
    import junit.framework.Assert.assertEquals
    import junit.framework.Assert.assertNotNull
    import org.junit.Before
    import org.junit.Test
    import org.junit.runner.RunWith
    import org.springframework.beans.factory.annotation.Autowired
    import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
    import org.springframework.boot.test.context.SpringBootTest
    import org.springframework.test.context.ContextConfiguration
    import org.springframework.test.context.junit4.SpringRunner
    
    
    @RunWith(SpringRunner::class)
    @SpringBootTest
    @ContextConfiguration(initializers = [DbTestInitializer::class])
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    class SampleRepositoryTest {
    
        @Autowired
        lateinit var sampleMapRepository: SampleMapRepository
    
        @Autowired
        lateinit var sampleJsonNodeRepository: SampleJsonNodeRepository
    
        lateinit var dto: SamplePojo
        lateinit var mapEntity: SampleMapEntity
        lateinit var jsonNodeEntity: SampleJsonNodeEntity
    
        @Before
        fun setUp() {
            dto = SamplePojo("Test", true)
            mapEntity = SampleMapEntity(null,
                    "POJO1",
                    dto.toMap()
            )
    
            jsonNodeEntity = SampleJsonNodeEntity(null,
                "POJO2",
                    jacksonObjectMapper().valueToTree(dto)
            )
        }
    
        @Test
        fun createMapPojo() {
            val id = sampleMapRepository.save(mapEntity).id!!
            assertNotNull(sampleMapRepository.getOne(id))
            assertEquals(sampleMapRepository.getOne(id).data?.let { SamplePojo(it) }, dto)
        }
    
        @Test
        fun createJsonNodePojo() {
            val id = sampleJsonNodeRepository.save(jsonNodeEntity).id!!
            assertNotNull(sampleJsonNodeRepository.getOne(id))
            assertEquals(jacksonObjectMapper().treeToValue(sampleJsonNodeRepository.getOne(id).data, SamplePojo::class.java), dto)
        }
    
    }
    

提交回复
热议问题