如何用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
相关产品推荐
相关产品推荐

