【问题标题】:How to create custom jsonb mapper for PostgreSQL and Hibernate in Kotlin?如何在 Kotlin 中为 PostgreSQL 和 Hibernate 创建自定义 jsonb 映射器?
【发布时间】:2020-05-14 22:57:06
【问题描述】:

我浏览了可用的文章:1、2、3。

所有文章都归结为以下选项:

  • 注册具有jsonb类型的自定义PostgreSQL95Dialect
  • 使用自定义映射实现 Hibernate 的 UserTypeinterface
  • 用自定义实现的@TypeDef注释Entity
  • 在 application.properties 自定义方言中定义

如果上述所有操作都完成,代码应该可以工作。就我而言,我遇到了神秘的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,我不知道如何进一步调试。

我的JsonbType抽象类:

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

我的具体班级BookHeaderType 看起来:

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
@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 实现可序列化

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

我做错了什么? jsonb 自定义类型是否应该在 Kotlin 中以不同方式创建?

【问题讨论】:

  • 也许还发布您的表定义。
  • 你的 BookBody 是什么?
  • 你解决了@DimytroChasovskyi 的问题吗?
  • @paranza 是的,我解决了。我基本上放弃了Hibernate,取了JdbcTemplate。不值得花时间。另外,我刚刚再次谷歌搜索了这个问题,发现hackish alternative。
  • @paranza 你还在为这个问题而苦恼吗?您能否添加一个指向将显示问题的引导项目的链接,以便我们可以更新此问题并创建答案,以便其他人也可以从中受益?

标签: postgresql hibernate kotlin jsonb


【解决方案1】:

有一个兄弟问题有the answer

您需要使用自定义类型:

pom.xml 依赖:

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

注册客户 PostgreSQL 方言:

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

选项 1:

使用jsonb 类型注释实体并将jsonb 设为Map&lt;String, Any&gt;:

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

你需要 Pojo 来映射序列化器/反序列化器。

选项 2:

使用jsonb 类型注释实体并将jsonb 设为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)
}

您需要自定义 POJO 到 JsonNode,假设是 Jackson 序列化器/反序列化器。

总结:

当您有一个带有一级缩进的大 JSON 时,第一个选项会更好。

当您在对象数据类型中有嵌套对象时,第二个选项会更好。

【讨论】:

    猜你喜欢
    • 2012-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-26
    • 2022-12-14
    • 2017-10-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多