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

JOOQ:如何将JSONB列映射到Kotlin数据类的List字段?

问题分析

报错的核心原因是:JOOQ默认将jsonb列映射为JSONB类型,当调用record.into(TrackedEvent::class.java)时,无法自动将JSON数组转换为List<Metadata>,而是解析成了LinkedHashMap的集合,Jackson序列化时因类型不匹配抛出异常。使用Array能正常运行是因为JOOQ对具体数组类型的映射支持更完善,但List作为接口需要额外配置类型转换逻辑。

解决方案

方案1:自定义JOOQ类型转换器(推荐)

通过自定义Converter实现JSONB与List<Metadata>的双向自动转换,一劳永逸解决映射问题,无需手动处理JSON序列化/反序列化。

1. 实现Converter类

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.core.type.TypeReference
import org.jooq.Converter
import org.jooq.JSONB

class MetadataListConverter : Converter<JSONB, List<Metadata>> {
    private val objectMapper = ObjectMapper().registerModule(KotlinModule())

    // 从数据库JSONB转为List<Metadata>
    override fun from(databaseObject: JSONB?): List<Metadata> {
        return databaseObject?.let {
            objectMapper.readValue(it.data(), object : TypeReference<List<Metadata>>() {})
        } ?: emptyList()
    }

    // 从List<Metadata>转为数据库JSONB
    override fun to(userObject: List<Metadata>?): JSONB {
        return userObject?.let {
            JSONB.jsonb(objectMapper.writeValueAsString(it))
        } ?: JSONB.jsonb("[]")
    }

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

2. 配置JOOQ应用转换器

方式A:代码生成阶段配置(适用于自动生成的表对象)

在JOOQ代码生成的XML配置中添加强制类型映射:

<forcedTypes>
    <forcedType>
        <userType>java.util.List&lt;com.my.project.Metadata&gt;</userType>
        <converter>com.my.project.MetadataListConverter</converter>
        <includeExpression>tracked_event\.metadata</includeExpression>
    </forcedType>
</forcedTypes>

方式B:手动表对象配置(适用于手动定义的表结构)

在TRACKED_EVENT表的字段定义中指定转换器:

import org.jooq.Field
import org.jooq.impl.SQLDataType

val METADATA: Field<List<Metadata>> = 
    Field("metadata", SQLDataType.JSONB.asConvertedDataType(MetadataListConverter()))

3. 简化插入代码

配置完成后,无需手动处理JSON转换,直接使用JOOQ的自动映射:

fun createTrackedEvent(trackedEvent: TrackedEvent): TrackedEvent {
    val record = dslContext.newRecord(TRACKED_EVENT, trackedEvent)
    record.store()
    return record.into(TrackedEvent::class.java)
}

方案2:手动转换返回结果(快速临时方案)

若不想修改JOOQ配置,可在存储后手动将JSONB解析为List<Metadata>,再构建TrackedEvent对象:

fun createTrackedEvent(trackedEvent: TrackedEvent): TrackedEvent {
    val record = dslContext.newRecord(TRACKED_EVENT, trackedEvent)
    record.metadata = JSONB.jsonb(objectMapper.writeValueAsString(trackedEvent.metadata))
    record.store()
    
    // 手动解析JSONB为List<Metadata>
    val metadataList = objectMapper.readValue(
        record.metadata.data(),
        object : TypeReference<List<Metadata>>() {}
    )
    
    return TrackedEvent(
        id = record.id,
        userId = record.userId,
        // 填充其他字段
        metadata = metadataList
    )
}

内容的提问来源于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 03:09:25