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

Kotlin Exposed一对多关联配置问题:关联数据获取返回null

Order与OrderProductAmount一对多关联返回null问题修复

问题描述

需要在Order实体与OrderProductAmount实体间构建一对多关联,用于记录每个Order包含的Product及其amount(一个订单可包含多个商品)。但从数据库获取Order实体后,访问order.products时返回null,怀疑是实体配置或数据插入方式有误。

相关代码

Order Entity

class Order(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<Order>(Orders)

    var orderStatus by Orders.orderStatus
    val products by OrderProductAmount referrersOn OrderProductAmounts.order
}
object Orders : IntIdTable() {
    var orderStatus = enumeration("orderStatus", OrderStatus::class)
}

OrderProductAmount Entity

class OrderProductAmount(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<OrderProductAmount>(OrderProductAmounts)

    var order by Order referencedOn OrderProductAmounts.order
    var product by OrderProductAmounts.product
    var amount by OrderProductAmounts.amount
}

object OrderProductAmounts : IntIdTable() {
    var order = reference("order", Orders)
    var product = integer("product")
    var amount = integer("amount")
}

Product Entity

class Product(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<Product>(Products)

    var name by Products.name
    var price by Products.price
}

object Products : IntIdTable() {
    val name = varchar("name", length = 256)
    val price = decimal("price", precision = 10, scale = 2)
}

插入新实体代码

override suspend fun addOrder(productIdToAmount: Map<Int, Int>) = dbQuery {
    val orderId = Orders.insertAndGetId {
        it[Orders.orderStatus] = OrderStatus.CREATED
    }
    productIdToAmount.entries.forEach { (productId, amount) -> 
        val product = productDAO.findProduct(productId)!!
        OrderProductAmounts.insert {
            it[OrderProductAmounts.order] = orderId.value
            it[OrderProductAmounts.product] = product.id.value
            it[OrderProductAmounts.amount] = amount
        }
    }
}

查询订单代码

override suspend fun findOrder(id: Int): Order? = dbQuery {
    Orders.select { Orders.id eq id }.map { Order.wrapRow(it) }.singleOrNull()
}

问题分析与修复方案

1. 插入数据时外键类型不匹配

插入OrderProductAmounts时,order字段应传入EntityID<Int>类型的orderId,而非其对应的Int值。虽然Exposed支持自动转换,但直接使用EntityID能确保关联关系的正确性,避免潜在的类型映射问题。

修改后的插入代码:

override suspend fun addOrder(productIdToAmount: Map<Int, Int>) = dbQuery {
    val orderId = Orders.insertAndGetId {
        it[orderStatus] = OrderStatus.CREATED
    }
    productIdToAmount.entries.forEach { (productId, amount) -> 
        val product = productDAO.findProduct(productId)!!
        OrderProductAmounts.insert {
            it[order] = orderId // 直接传入EntityID
            it[product] = product.id.value
            it[amount] = amount
        }
    }
}

2. 查询方式未正确初始化关联属性

手动使用select+wrapRow创建Order实体时,无法正确初始化关联的products属性。改用Exposed提供的findById方法,它会自动处理实体关联上下文,确保关联属性能正常加载。

修改后的查询代码:

override suspend fun findOrder(id: Int): Order? = dbQuery {
    Order.findById(id)
}

3. 验证关联配置与数据库数据

  • 确认Order实体中的products定义:val products by OrderProductAmount referrersOn OrderProductAmounts.order,该配置正确,与OrderProductAmount中的order关联形成一对多关系。
  • 检查数据库order_product_amount表,确认对应订单的关联记录存在,且外键order的值与订单ID一致。若数据库无关联数据,products会返回空集合而非null;若仍返回null,说明实体初始化异常,使用findById即可解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 16:40:39