1

I went through available articles: 1, 2, 3.

All of the articles nail down to the following options:

  • Register custom PostgreSQL95Dialect which has jsonb type
  • Implement Hibernate's UserTypeinterface with custom mapping
  • Annotate Entity with @TypeDef of custom implementation
  • Define in application.properties custom dialect

If all of the above is done, code is supposed to work. In my case I bump into mysterious Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: property mapping has wrong number of columns: com.example.Book.header type: com.example.hibernate.BookHeaderType which I don't understand how to debug further.

My JsonbType abstract class:

abstract class JsonbType : UserType {

    override fun hashCode(p0: Any?): Int {
        return p0!!.hashCode()
    }

    override fun deepCopy(p0: Any?): Any {
        return try {
            val bos = ByteArrayOutputStream()
            val oos = ObjectOutputStream(bos)
            oos.writeObject(p0)
            oos.flush()
            oos.close()
            bos.close()
            val bais = ByteArrayInputStream(bos.toByteArray())
            ObjectInputStream(bais).readObject()
        } catch (ex: ClassNotFoundException) {
            throw HibernateException(ex)
        } catch (ex: IOException) {
            throw HibernateException(ex)
        }
    }

    override fun replace(p0: Any?, p1: Any?, p2: Any?): Any {
        return deepCopy(p0)
    }

    override fun equals(p0: Any?, p1: Any?): Boolean {
        return p0 == p1
    }

    override fun assemble(p0: Serializable?, p1: Any?): Any {
        return deepCopy(p0)
    }

    override fun disassemble(p0: Any?): Serializable {
        return deepCopy(p0) as Serializable
    }

    override fun nullSafeSet(p0: PreparedStatement?, p1: Any?, p2: Int, p3: SharedSessionContractImplementor?) {
        if (p1 == null) {
            p0?.setNull(p2, Types.OTHER)
            return
        }
        try {
            val mapper = ObjectMapper()
            val w = StringWriter()
            mapper.writeValue(w, p1)
            w.flush()
            p0?.setObject(p2, w.toString(), Types.OTHER)
        } catch (ex: java.lang.Exception) {
            throw RuntimeException("Failed to convert Jsonb to String: " + ex.message, ex)
        }
    }

    override fun nullSafeGet(p0: ResultSet?, p1: Array<out String>?, p2: SharedSessionContractImplementor?, p3: Any?): Any {
        val cellContent = p0?.getString(p1?.get(0))
        return try {
            val mapper = ObjectMapper()
            mapper.readValue(cellContent?.toByteArray(charset("UTF-8")), returnedClass())
        } catch (ex: Exception) {
            throw RuntimeException("Failed to convert String to Jsonb: " + ex.message, ex)
        }
    }

    override fun isMutable(): Boolean {
        return true
    }

    override fun sqlTypes(): kotlin.IntArray? {
        return IntArray(Types.JAVA_OBJECT)
    }
}

My concrete class BookHeaderType looks:

class BookHeaderType : JsonbType() {

    override fun returnedClass(): Class<BookBody> {
        return BookBody::class.java
    }

}

CustomPostgreSQLDialect.kt:

class CustomPostgreSQLDialect : PostgreSQL95Dialect {

    constructor(): super() {
        this.registerColumnType(Types.JAVA_OBJECT, "jsonb")
    }
}

Book.kt entity:

@Entity
@Table(name = "book")
@TypeDefs(
        TypeDef(name = "BookHeaderType", typeClass = BookHeaderType::class)
)
data class Book(
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        @Column(updatable = false, nullable = false)
        val id: Long,

        @Column(name = "header", nullable = false, columnDefinition = "jsonb")
        @Type(type = "BookHeaderType")
        var header: BookHeader
)

BookHeader.kt implements Serializable

@JsonIgnoreProperties(ignoreUnknown = true)
data class BookHeader(
    var createdAt: OffsetDateTime,
    var createdBy: String
) : Serializable {
    constructor() : this(OffsetDateTime.now(), "test")
}

What do I do wrong? Should jsonb custom type be created differently in Kotlin?

6
  • Maybe also post your table definition. Commented Jan 30, 2020 at 7:35
  • what is your BookBody? Commented Apr 20, 2020 at 15:19
  • Have you solved the issue @DimytroChasovskyi? Commented Apr 20, 2020 at 15:20
  • @paranza Yes, I solved. I basically give up on Hibernate and took JdbcTemplate. It didn't worth time-spending. Also, I just Googled this problem again and found hackish alternative. Commented Apr 20, 2020 at 16:08
  • @paranza Are you still struggling with this issue? Can you add a link to the bootstrapped project that will show an issue, so we can update this question and create answer, so other people can benefit from it as well? Commented Apr 24, 2020 at 11:36

1 Answer 1

1

There is a sibling question that has the answer

You would need to use custom types:

pom.xml dependency:

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

Register Customer PostgreSQL dialect:

class CustomPostgreSQLDialect : PostgreSQL95Dialect {
  constructor() : super() {
    this.registerHibernateType(Types.OTHER, JsonNodeBinaryType::class.java.name)
    this.registerHibernateType(Types.OTHER, JsonStringType::class.java.name)
    this.registerHibernateType(Types.OTHER, JsonBinaryType::class.java.name)
    this.registerHibernateType(Types.OTHER, JsonNodeBinaryType::class.java.name)
    this.registerHibernateType(Types.OTHER, JsonNodeStringType::class.java.name)
  }
}

Option 1:

Annotate Entity with jsonb type and make jsonb as Map<String, Any>:

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)
}

You would need Pojo to Map serializer/deserializer.

Option 2:

Annotate Entity with jsonb type and make jsonb as JsonNode?:

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)
}

You would need custom POJO to JsonNode, assumingly Jackson serializer/deserializer.

Summary:

1st option is better when you have a big JSON with one level of indentation.

2nd option is better when you have nested objects in objects data-types.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.