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

后端 未结 2 1175
南旧
南旧 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<String, Any>?
    ) {
    
        /**
         * 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<String, Any> 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<String, Any>) : this(map["payload"] as String, map["flag"] as Boolean)
    
        fun toMap() : Map<String, Any> {
            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<String, Any>, 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<SampleJsonNodeEntity, Long> {
    }
    

    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)
        }
    
    }
    
    0 讨论(0)
  • 2020-12-19 21:40

    Extending with an example, sorry I know I am a bit late

    in your pom.xml:

    <dependency>
        <groupId>com.vladmihalcea</groupId>
        <artifactId>hibernate-types-52</artifactId>
        <version>2.4.3</version>
    </dependency>
    

    Then I have my entity named Day:

    import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
    
    @TypeDefs({
            @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
    })
    @Data
    @Entity
    public class Day {
    
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       @Column(name = "DayId")
       private Integer id;
       private Integer day;
       private Integer month;
       private Integer year;
    
       @Type(type = "jsonb")
       @Column(columnDefinition = "jsonb")
       private List<Activity> activities;
    
       @Type(type = "jsonb")
       @Column(columnDefinition = "jsonb")
       private Notification notification;
    
    }
    

    Activity and Notification JSONB's class:

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Activity implements Serializable {
    
       private String name;
       private String emoji;
       private Integer durationInSeconds;
       private Boolean highPriority;
    
       public Activity (){}
    }
    
    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Notification implements Serializable {
    
        private String email;
        private String mobile;
    
        public Notification (){}
    }
    

    Our repository:

    @Repository
    public interface DayRepository extends CrudRepository<Day, Integer> {
    
    }
    

    Our service:

    public interface DayService{
        Day saveArbitraryDay();
    }
    
    @Service
    @Transactional
    public DayServiceImpl implements DayService{
    
        private DayRepository repository;
    
        public DayServiceImpl(DayRepository repository){
             this.repository = repository;
        }
    
        @Override
        public Day saveArbitraryDay(){
             Day day = new Day();
             day.setDay(16);
             day.setMonth(04);
             day.setYear(1991);
    
             //Set the jsonb objects
             //You can use custom constructors whatever
             Notification notification = new Notification();
             notification.setEmail("contoso@hotmail.com");
             day.setNotification(notification);
    
             //Now putting activities
             List<Activity> activities = new ArrayList<>();
    
             Activity actOne = new Activity();
             actOne.setName("Breakfast");
             actOne.setEmoji("                                                                    
    0 讨论(0)
提交回复
热议问题