You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Hibernate正确映射PostgreSQL自定义复合类型(Quarkus+Kotlin)

问题描述

在Quarkus、Kotlin和Panache Repository技术栈下,尝试将PostgreSQL自定义复合类型映射到Hibernate实体时触发SQL语法错误。

PostgreSQL配置

CREATE TYPE money_and_currency AS (amount integer, currency varchar(3));
CREATE TABLE example_table (
    "id" uuid,
    "amount" money_and_currency,
);

当前Hibernate实体配置

@Entity
@Table(name = "example_table")
class ScheduledTransactionEntity : PanacheEntityBase {
    @Id
    @GeneratedValue
    @Column(columnDefinition = "uuid", updatable = false)
    var id: UUID? = null

    @Column(nullable = false)
    @Embedded
    var amount: MoneyAndCurrency? = null
}

@Embeddable
data class MoneyAndCurrency(val amount: Int, val currency: String)

报错信息

org.hibernate.exception.SQLGrammarException: could not execute statement [ERROR: column "currency" of relation "scheduled_transactions" does not exist

尝试在@Column中添加columnDefinition="money_and_currency"后问题仍未解决,需明确PostgreSQL自定义复合类型与Hibernate实体的正确映射方式。


解决方案

核心问题在于@Embedded注解的误用:Hibernate会将嵌入式对象的字段拆分为数据库表中的独立列,但你的PostgreSQL表中amount是单个复合类型列,因此必须通过自定义Hibernate UserType实现双向转换。

步骤1:实现Hibernate UserType

创建自定义类型类,负责Kotlin数据类与PostgreSQL复合类型的相互转换:

import org.hibernate.engine.spi.SharedSessionContractImplementor
import org.hibernate.usertype.UserType
import org.postgresql.util.PGStruct
import java.io.Serializable
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Types

class MoneyAndCurrencyUserType : UserType {

    override fun returnedClass(): Class<MoneyAndCurrency> = MoneyAndCurrency::class.java

    override fun equals(x: Any?, y: Any?): Boolean {
        if (x === y) return true
        if (x == null || y == null) return false
        return x == y
    }

    override fun hashCode(x: Any?): Int = x?.hashCode() ?: 0

    override fun nullSafeGet(
        rs: ResultSet,
        names: Array<out String>,
        session: SharedSessionContractImplementor,
        owner: Any?
    ): MoneyAndCurrency? {
        val struct = rs.getObject(names[0]) as PGStruct? ?: return null
        val attributes = struct.values
        return MoneyAndCurrency(attributes[0] as Int, attributes[1] as String)
    }

    override fun nullSafeSet(
        st: PreparedStatement,
        value: Any?,
        index: Int,
        session: SharedSessionContractImplementor
    ) {
        if (value == null) {
            st.setNull(index, Types.OTHER)
            return
        }
        val money = value as MoneyAndCurrency
        val struct = session.connection().createStruct(
            "money_and_currency",
            arrayOf(money.amount, money.currency)
        )
        st.setObject(index, struct)
    }

    override fun deepCopy(value: Any?): Any? = value?.let { (it as MoneyAndCurrency).copy() }

    override fun isMutable(): Boolean = false

    override fun disassemble(value: Any?): Serializable? = value as Serializable

    override fun assemble(cached: Serializable?, owner: Any?): Any? = cached

    override fun replace(original: Any?, target: Any?, owner: Any?): Any? = original

    override fun sqlTypes(): IntArray = intArrayOf(Types.OTHER)
}

步骤2:修改实体映射配置

移除@Embedded注解,改用@Type指定自定义类型,并明确列的复合类型定义:

import org.hibernate.annotations.Type

@Entity
@Table(name = "example_table")
class ScheduledTransactionEntity : PanacheEntityBase {
    @Id
    @GeneratedValue
    @Column(columnDefinition = "uuid", updatable = false)
    var id: UUID? = null

    @Column(nullable = false, columnDefinition = "money_and_currency")
    @Type(type = "com.yourpackage.MoneyAndCurrencyUserType") // 替换为实际包路径
    var amount: MoneyAndCurrency? = null
}

// 移除@Embeddable注解
data class MoneyAndCurrency(val amount: Int, val currency: String)

步骤3:Quarkus环境适配(可选)

若需Quarkus自动识别自定义类型,在application.properties中添加配置:

quarkus.hibernate-orm.custom-types.contribute=true

关键说明

  • @Embedded仅适用于映射到数据库多列的嵌入式对象,PostgreSQL复合类型是单一列,必须通过自定义UserType处理。
  • 自定义类型需实现JDBC Struct与Kotlin对象的双向转换,确保Hibernate能正确读写复合类型列。

内容的提问来源于stack exchange,提问作者Sherlock Wayne

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 20:13:11