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

使用Ktor与Exposed实现表关联CRUD时遇插入异常求助

解决Ktor + Exposed关联CRUD中的UnsupportedNullValuesException及相关问题

首先,咱们先拆解你遇到的核心错误:io.ktor.gson.UnsupportedNullValuesException: Receiving null values is not supported,这个问题直接来自Ktor的Gson转换器——默认情况下它拒绝处理请求体中的null值,再结合你代码里的几个小问题,导致了插入失败。下面一步步帮你解决:

1. 先搞定Gson的null值处理问题

你有两个方向可以选:

方案A:让Gson允许接收null值

在Ktor的ContentNegotiation配置里,给Gson添加serializeNulls()配置:

install(ContentNegotiation) {
    gson {
        serializeNulls() // 允许序列化/反序列化null值
        setLenient() // 可选,处理格式不严格的JSON
    }
}

方案B:调整模型类字段为可空类型

如果你的业务场景中某些字段确实可能为空(比如邮箱),把模型类里的对应字段改成可空类型,同时更新Exposed表定义允许null:

// 模型类
data class LegalPerson(
    // ...其他字段
    val email: String? // 改成可空
)

// Exposed表定义
object LegalPersons : Table("person.legal_person") {
    // ...其他字段
    val email: Column<String?> = varchar("email", 100).nullable() // 允许null
}

2. 修复代码里的明显笔误

在你的LegalPersonService.insert方法里,有个复制粘贴错误:

// 错误代码
it[email] = lp.fantasyName
// 应该改成
it[email] = lp.email

这个错误不仅会导致邮箱字段被错误赋值,还可能因为lp.email如果是null(但模型类里是non-null)触发Gson的null异常。

3. 修正Phone模型的不合理字段类型

你的Phone数据类里phone字段是UUID类型,这完全不符合电话号码的存储逻辑,电话号码应该是字符串!赶紧修正:

// 模型类
data class Phone(
    val id: UUID,
    val internalId: Long,
    val phone: String // 改成String类型
)

// Exposed表定义
object Phones : Table("person.phone_legal_person") {
    val id: Column<UUID> = uuid("id").autoIncrement().primaryKey()
    // 添加外键关联LegalPerson,代替原来把id作为reference的不合理设计
    val legalPersonId: Column<UUID> = reference("legal_person_id", LegalPersons.id)
    val internalId: Column<Long> = long("internal_id").autoIncrement()
    val phone: Column<String> = varchar("phone", 20) // 改成varchar类型
}

原来的Phones.id直接关联LegalPersons.id作为主键,意味着一个法人只能有一个手机号,如果你需要一对多的关联(一个法人多个手机号),上面的修改更合理;如果确实是一对一,也建议保留独立主键,用外键关联。

4. 修正Exposed的UUID自增配置

Exposed的uuid().autoIncrement()并不是所有数据库都支持(比如PostgreSQL需要额外配置),你可以改成手动生成UUID或者用数据库默认值:

// 手动生成UUID作为默认值
val id: Column<UUID> = uuid("id").default(UUID.randomUUID()).primaryKey()

// 或者用PostgreSQL的uuid_generate_v4()函数(需要先安装uuid-ossp扩展)
val id: Column<UUID> = uuid("id").default(Expression.build { "uuid_generate_v4()" }).primaryKey()

5. 完善关联查询和插入逻辑

关联查询

你的toLp方法里创建了Phone对象,但LegalPerson模型类里没有对应的字段,先给LegalPerson添加phone字段:

data class LegalPerson(
    // ...其他字段
    val phone: Phone // 添加关联的Phone字段
)

然后修改查询逻辑,用join关联两张表:

suspend fun findAll(): List<LegalPerson> = dbQuery {
    LegalPersons.join(Phones, JoinType.INNER, LegalPersons.id, Phones.legalPersonId)
        .selectAll()
        .map { toLp(it) }
}

关联插入

插入法人后,把生成的法人ID关联到手机号的外键字段:

suspend fun insert(lp: LegalPerson, ph: Phone) = dbQuery {
    // 获取插入后的法人ID
    val insertedLpId = LegalPersons.insert {
        it[id] = lp.id
        it[internalId] = lp.internalId
        it[companyId] = lp.companyId
        it[active] = lp.active
        it[tradeName] = lp.tradeName
        it[fantasyName] = lp.fantasyName
        it[email] = lp.email
        it[cnpj] = lp.cnpj
        it[stateRegistration] = lp.stateRegistration
        it[muninipalRegistration] = lp.muninipalRegistration
        it[address] = lp.address
    } get LegalPersons.id

    // 插入手机号时关联法人ID
    Phones.insert {
        it[id] = ph.id
        it[legalPersonId] = insertedLpId
        it[internalId] = ph.internalId
        it[phone] = ph.phone
    }
}

按上面的步骤修改后,你的CRUD关联操作应该就能正常运行了,如果还有问题,可以检查请求体的JSON格式是否和模型类匹配,或者数据库的表结构是否和Exposed定义一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:22:13