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

Quarkus迁移至Hibernate Reactive时JSONB字段映射异常问题

Hibernate Reactive JSONB字段映射问题处理方案

问题本质

这不是Bug,而是Hibernate Reactive对自定义类型的处理有特殊要求。传统Hibernate ORM基于JDBC驱动,而Hibernate Reactive依赖Vert.x异步SQL客户端,两者的类型绑定、转换机制完全不同。你使用的quarkus-hibernate-types:0.2.0中的JsonBinaryType仅适配阻塞式JDBC场景,没有实现Hibernate Reactive所需的异步类型处理接口,导致参数无法被Vert.x SQL客户端正确解析。

解决方法

方案1:自定义适配Reactive的JSON类型

实现Hibernate Reactive要求的ReactiveType接口,让JSON类型能适配Vert.x的异步参数处理逻辑:

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import org.hibernate.type.descriptor.WrapperOptions
import org.hibernate.type.descriptor.java.JavaTypeDescriptor
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor
import org.hibernate.type.descriptor.sql.ValueBinder
import org.hibernate.type.descriptor.sql.ValueExtractor
import org.hibernate.type.json.JsonBinaryType
import org.hibernate.reactive.type.ReactiveType
import java.sql.*
import java.sql.Types.JAVA_OBJECT

class ReactiveJsonBinaryType : JsonBinaryType(), ReactiveType<JsonNode> {
    private val objectMapper = ObjectMapper()

    override fun getReactiveJavaTypeDescriptor(): JavaTypeDescriptor<JsonNode> {
        return object : JavaTypeDescriptor<JsonNode> {
            override fun getJavaType(): Class<JsonNode> = JsonNode::class.java

            override fun unwrap(value: JsonNode, context: WrapperOptions): Any {
                // 将JsonNode转为Vert.x能处理的字符串
                return objectMapper.writeValueAsString(value)
            }

            override fun wrap(value: Any, context: WrapperOptions): JsonNode {
                // 将数据库返回的对象转回JsonNode
                return objectMapper.readTree(value.toString())
            }
        }
    }

    override fun getReactiveSqlTypeDescriptor(): SqlTypeDescriptor {
        return object : SqlTypeDescriptor {
            override fun getSqlType(): Int = JAVA_OBJECT
            override fun canBeRemapped(): Boolean = true

            override fun getBinder(bindingType: BindingType): ValueBinder<*> {
                return object : ValueBinder<JsonNode> {
                    override fun bind(
                        st: PreparedStatement,
                        value: JsonNode,
                        index: Int,
                        options: WrapperOptions
                    ) {
                        st.setObject(index, unwrap(value, options), Types.JSON)
                    }

                    override fun bind(
                        st: CallableStatement,
                        value: JsonNode,
                        name: String,
                        options: WrapperOptions
                    ) {
                        st.setObject(name, unwrap(value, options), Types.JSON)
                    }
                }
            }

            override fun getExtractor(extractionType: ExtractionType): ValueExtractor<*> {
                return object : ValueExtractor<JsonNode> {
                    override fun extract(
                        rs: ResultSet,
                        name: String,
                        options: WrapperOptions
                    ): JsonNode? {
                        val value = rs.getObject(name)
                        return value?.let { wrap(it, options) }
                    }

                    override fun extract(
                        rs: ResultSet,
                        index: Int,
                        options: WrapperOptions
                    ): JsonNode? {
                        val value = rs.getObject(index)
                        return value?.let { wrap(it, options) }
                    }

                    override fun extract(
                        cs: CallableStatement,
                        index: Int,
                        options: WrapperOptions
                    ): JsonNode? {
                        val value = cs.getObject(index)
                        return value?.let { wrap(it, options) }
                    }

                    override fun extract(
                        cs: CallableStatement,
                        name: String,
                        options: WrapperOptions
                    ): JsonNode? {
                        val value = cs.getObject(name)
                        return value?.let { wrap(it, options) }
                    }
                }
            }
        }
    }
}

更新实体类的类型定义:

@Entity
@TypeDef(name = JsonTypes.JSON_BIN, typeClass = ReactiveJsonBinaryType::class)
class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "myEntityIdGenerator")
    @SequenceGenerator(name = "myEntityIdGenerator", sequenceName = "my_entity_id_seq", allocationSize = 10)
    var id: Long? = null

    @Type(type = JsonTypes.JSON_BIN)
    @NotNull
    lateinit var jsonData: JsonNode
}

方案2:利用Hibernate Reactive原生JSON支持(PostgreSQL场景)

如果你的数据库是PostgreSQL,可以直接依赖Hibernate Reactive对JSONB的原生支持,无需第三方类型库:

  1. 调整实体类字段标注:
@Entity
class MyEntity {
    // ... 其他字段

    @Column(columnDefinition = "jsonb")
    @NotNull
    lateinit var jsonData: JsonNode
}
  1. 确保项目依赖quarkus-hibernate-reactive和对应数据库驱动扩展(如quarkus-reactive-pg-client),Hibernate Reactive会自动通过Jackson处理JsonNode的序列化与反序列化。

总结

Hibernate Reactive的异步模型与传统ORM的阻塞JDBC模型差异较大,自定义类型必须实现ReactiveType接口才能适配其底层的Vert.x SQL客户端。你遇到的问题属于场景适配问题,而非Bug。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:09:28