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

Room数据库同表多对多关联嵌套POJO引发StackOverflowError求助

解决Room递归嵌套实体导致的StackOverflowError

问题场景

我维护一套产品数据库结构:产品表可关联备件列表,每个产品的用户手册存储在附件表中。使用Room定义实体和关联POJO时,在PopulatedProduct类中通过@Relation将备件声明为List<PopulatedProduct>,编译时触发java.lang.StackOverflowError。若将备件类型替换为ProductEntity可正常编译,但无法获取备件关联的用户手册,需要保留嵌套的完整信息。

相关实体及POJO代码如下:

产品实体类

@Entity(
    tableName = "products", foreignKeys = [
        ForeignKey(
            entity = AttachmentEntity::class,
            parentColumns = ["id"],
            childColumns = ["attachment_id"],
            onDelete = ForeignKey.CASCADE
        ),
    ]
)
data class ProductEntity(
    @PrimaryKey
    val id: Int,
    @ColumnInfo(name = "attachment_id")
    val attachmentId: Int,
)

附件实体类

@Entity(
    tableName = "attachment"
)
data class AttachmentEntity(
    @PrimaryKey
    val id: Int
)

产品关联表

@Entity(
    tableName = "products_products",
    primaryKeys = ["product_id1", "product_id2"],
    foreignKeys = [
        ForeignKey(
            entity = ProductEntity::class,
            parentColumns = ["id"],
            childColumns = ["product_id1"],
            onDelete = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = ProductEntity::class,
            parentColumns = ["id"],
            childColumns = ["product_id2"],
            onDelete = ForeignKey.CASCADE
        ),
    ]
)
data class ProductProductCrossRef(
    @ColumnInfo(name = "product_id1")
    val productId1: Int,
    @ColumnInfo(name = "product_id2")
    val productId2: Int,
)

报错的POJO类

data class PopulatedProduct(
    @Embedded
    val entity: ProductEntity,

    @Relation(
        parentColumn = "attachment_id",
        entityColumn = "id"
    )
    val userManual: AttachmentEntity?,

    @Relation(
        entity = ProductEntity::class,
        parentColumn = "id",
        entityColumn = "id",
        associateBy = Junction(
            value = ProductProductCrossRef::class,
            parentColumn = "product_id1",
            entityColumn = "product_id2",
        )
    )
    val spareparts: List<PopulatedProduct> // 此处触发StackOverflowError
)

问题原因

Room的@Relation注解不支持递归嵌套实体(即实体/POJO中包含自身类型的关联字段)。编译阶段Room会尝试解析嵌套结构,无限递归生成查询逻辑,最终超出栈内存限制,抛出StackOverflowError。

解决方案

方案1:拆分POJO类,避免递归引用

创建两个层级的POJO,将产品+手册的基础信息与备件关联拆分,从根源避免递归:

// 基础POJO:封装产品实体+用户手册
data class ProductWithManual(
    @Embedded val entity: ProductEntity,
    @Relation(
        parentColumn = "attachment_id",
        entityColumn = "id"
    )
    val userManual: AttachmentEntity?
)

// 扩展POJO:基础信息+备件列表(备件使用基础POJO)
data class PopulatedProduct(
    @Embedded val productWithManual: ProductWithManual,
    @Relation(
        entity = ProductEntity::class,
        parentColumn = "id",
        entityColumn = "id",
        associateBy = Junction(
            value = ProductProductCrossRef::class,
            parentColumn = "product_id1",
            entityColumn = "product_id2"
        )
    )
    val spareparts: List<ProductWithManual>
)

这种方案既保留了备件的用户手册信息,又避免了递归引用,编译和运行都能正常工作。

方案2:@Transaction手动实现递归查询(支持深层嵌套)

如果需要支持多层级备件嵌套(比如备件自身也包含备件),可以在Dao中通过@Transaction手动编写递归查询逻辑,完全控制查询流程:

@Dao
interface ProductDao {
    @Transaction
    suspend fun getPopulatedProduct(productId: Int, maxDepth: Int = 3): PopulatedProduct? {
        // 限制递归深度,避免运行时栈溢出
        if (maxDepth <= 0) return null
        
        // 查询当前产品及手册
        val product = getProductWithManual(productId) ?: return null
        // 查询当前产品的备件ID列表
        val sparePartIds = getSparePartIds(productId)
        // 递归查询每个备件的完整信息,深度减1
        val spareParts = sparePartIds.mapNotNull { getPopulatedProduct(it, maxDepth - 1) }
        
        return PopulatedProduct(product.entity, product.userManual, spareParts)
    }

    // 基础查询:获取产品+用户手册
    @Query("SELECT * FROM products WHERE id = :productId")
    suspend fun getProductWithManual(productId: Int): ProductWithManual?

    // 查询指定产品的备件ID列表
    @Query("SELECT product_id2 FROM products_products WHERE product_id1 = :productId")
    suspend fun getSparePartIds(productId: Int): List<Int>
}

注意:必须添加递归深度限制,防止运行时因无限递归触发栈溢出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 05:15:39