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

Jooq JsonConverter查询时无法将jsonb转为自定义类列表

问题原因与解决方案

核心原因

插入正常、查询失败的本质是序列化/反序列化时的类型信息差异:

  • 插入阶段:你传入的是明确的List<Metadata>实例,Jackson能直接识别具体类型并序列化为JSONB,jOOQ只负责把序列化后的字符串写入数据库,不需要做类型推断。
  • 查询阶段:jOOQ从数据库读取JSONB字符串后,默认的Jackson绑定只知道目标字段是List,但无法获取到泛型参数Metadata的类型信息,只能将JSON数组反序列化为List<LinkedHashMap>。当后续Spring要把TrackedEvent序列化为JSON返回时,Jackson发现metadata字段的实际类型是LinkedHashMap而非Metadata,就触发了类型不匹配的序列化报错。

解决方案

方案1:自定义jOOQ Binding处理泛型类型

创建专门的Binding,明确告诉Jackson要反序列化到List<Metadata>类型:

import org.jooq.*
import org.jooq.impl.DSL
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.module.kotlin.kotlinModule
import java.sql.*
import java.util.*

class MetadataListBinding : Binding<JSONB, List<Metadata>> {
    private val objectMapper = ObjectMapper().registerModule(kotlinModule())
    private val typeRef = object : TypeReference<List<Metadata>>() {}

    override fun converter(): Converter<JSONB, List<Metadata>> {
        return object : Converter<JSONB, List<Metadata>> {
            override fun from(t: JSONB?): List<Metadata>? {
                return t?.let { objectMapper.readValue(it.data(), typeRef) }
            }

            override fun from(u: List<Metadata>?): JSONB? {
                return u?.let { JSONB.valueOf(objectMapper.writeValueAsString(it)) }
            }

            override fun fromType(): Class<JSONB> = JSONB::class.java
            override fun toType(): Class<List<Metadata>> = List::class.java as Class<List<Metadata>>
        }
    }

    override fun sql(ctx: BindingSQLContext<List<Metadata>>) {
        ctx.render().visit(DSL.val(ctx.convert(converter()).value())).sql("::jsonb")
    }

    override fun register(ctx: BindingRegisterContext<List<Metadata>>) {
        ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR)
    }

    override fun set(ctx: BindingSetStatementContext<List<Metadata>>) {
        ctx.statement().setString(ctx.index(), ctx.convert(converter()).value()?.data())
    }

    override fun set(ctx: BindingSetSQLOutputContext<List<Metadata>>) {
        throw SQLFeatureNotSupportedException()
    }

    override fun get(ctx: BindingGetResultSetContext<List<Metadata>>) {
        ctx.convert(converter()).value(JSONB.valueOf(ctx.resultSet().getString(ctx.index())))
    }

    override fun get(ctx: BindingGetStatementContext<List<Metadata>>) {
        ctx.convert(converter()).value(JSONB.valueOf(ctx.statement().getString(ctx.index())))
    }

    override fun get(ctx: BindingGetSQLInputContext<List<Metadata>>) {
        throw SQLFeatureNotSupportedException()
    }
}

之后在查询时给metadata字段绑定这个处理类:

dsl.select(TRACKED_EVENT.ID, TRACKED_EVENT.METADATA.convert(MetadataListBinding()))
   .from(TRACKED_EVENT)
   .fetchInto(TrackedEvent::class.java)

方案2:查询时显式转换类型

如果不想自定义Binding,可以在查询环节手动用Jackson完成反序列化:

val objectMapper = ObjectMapper().registerModule(kotlinModule())
val typeRef = object : TypeReference<List<Metadata>>() {}

val events = dsl.select(TRACKED_EVENT.ID, TRACKED_EVENT.METADATA)
               .from(TRACKED_EVENT)
               .fetch { record ->
                   TrackedEvent(
                       id = record.getValue(TRACKED_EVENT.ID),
                       metadata = objectMapper.readValue(record.getValue(TRACKED_EVENT.METADATA).data(), typeRef)
                       // 其他字段按需补充
                   )
               }

方案3:确保Jackson Kotlin模块正确配置

检查Spring配置中是否注册了Jackson Kotlin模块,它能优化Jackson对Kotlin泛型和数据类的处理:

@Configuration
class JacksonConfig {
    @Bean
    fun objectMapper(): ObjectMapper {
        return ObjectMapper().registerModule(com.fasterxml.jackson.module.kotlin.KotlinModule.Builder().build())
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 06:06:45