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

ROOM存储含自引用Node类的Tree实体遇栈溢出,求助

问题原因

你遇到的栈溢出是因为Node类的循环引用导致序列化无限递归:每个Node持有parent引用,父Node又持有left/right子Node引用,当Room(底层用Gson序列化嵌套对象)尝试存储Tree时,会不断递归序列化父节点和子节点,直到栈空间耗尽。

解决方案

方案1:采用关系型数据库设计(推荐)

数据库不适合直接存储嵌套对象,正确的做法是把Node单独作为实体,用ID关联父子节点,Tree只存储根节点的ID:

修改实体类

@Entity(tableName = "trees")
data class Tree(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    // 存储根节点的ID,而非整个Node对象
    val rootNodeId: Int? = null
)

@Entity(
    tableName = "nodes",
    foreignKeys = [ForeignKey(
        entity = Node::class,
        parentColumns = ["id"],
        childColumns = ["parentId"],
        // 删除父节点时自动删除子节点,可根据需求调整
        onDelete = ForeignKey.CASCADE
    )]
)
@Parcelize
data class Node(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    // 存储左/右子节点的ID
    var leftNodeId: Int? = null,
    var rightNodeId: Int? = null,
    var isRoot: Boolean = false,
    var isLeft: Boolean? = null,
    var isRight: Boolean? = null,
    var hash: Int = 0,
    // 存储父节点的ID
    var parentId: Int? = null
) : Parcelable

构建树形结构

从数据库读取时,先查询根节点,再递归查询子节点来构建完整树形:

// Dao示例
@Dao
interface NodeDao {
    @Query("SELECT * FROM nodes WHERE id = :nodeId")
    suspend fun getNodeById(nodeId: Int): Node?

    @Query("SELECT * FROM nodes WHERE parentId = :parentId")
    suspend fun getChildrenByParentId(parentId: Int): List<Node>
}

// 构建树形的函数示例
suspend fun buildTree(rootNodeId: Int?, nodeDao: NodeDao): Node? {
    rootNodeId ?: return null
    val root = nodeDao.getNodeById(rootNodeId) ?: return null
    val children = nodeDao.getChildrenByParentId(root.id)
    children.forEach { child ->
        val childTree = buildTree(child.id, nodeDao)
        if (child.isLeft == true) {
            root.left = childTree
        } else if (child.isRight == true) {
            root.right = childTree
        }
        childTree?.parent = root
    }
    return root
}

方案2:忽略循环引用(不推荐,仅临时解决)

如果非要在Tree中存储完整Node对象,可以通过Gson注解忽略parent字段的序列化,避免递归:

@Parcelize
class Node(
    // 序列化时忽略parent字段,打破循环
    @JsonIgnore
    var parent: Node? = null,
    var left: Node? = null,
    var right: Node? = null,
    var isRoot: Boolean = false,
    var isLeft: Boolean? = null,
    var isRight: Boolean? = null,
    var hash: Int = 0,
) : Parcelable

⚠️ 注意:这种方式读取数据后,parent字段会为null,需要手动重新关联,维护成本高,不适合长期使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 06:25:27