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

