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

如何使用Room Relationships存储包含Books的Lists复杂对象?

解决Room一对多关系存储问题的正确实现方式

1. 调整实体类,建立外键关联

Room不支持在实体中直接嵌套对象列表,必须将Lists和Books拆分为独立实体,通过外键建立关联:

修改后的Lists实体

@Entity(tableName = "lists_table")
data class Lists(
    @PrimaryKey
    @ColumnInfo(name = "display_name")
    @Json(name = "display_name") val displayName: String
)

调整后的Books实体(添加外键)

注意:原代码用author作为主键存在逻辑漏洞(同一作者可能有多本不同书籍),这里改用author+title作为联合主键避免重复,同时新增外键字段关联对应List:

@Entity(
    tableName = "books_table",
    foreignKeys = [
        ForeignKey(
            entity = Lists::class,
            parentColumns = ["display_name"],
            childColumns = ["list_display_name"],
            onDelete = ForeignKey.CASCADE // 删除List时自动删除关联的Books
        )
    ],
    primaryKeys = ["author", "title"]
)
data class Books(
    @ColumnInfo(name = "author") val author: String,
    @ColumnInfo(name = "book_image") @Json(name = "book_image") val bookImage: String,
    @ColumnInfo(name = "width") @Json(name = "book_image_width") val imageWidth: Int,
    @ColumnInfo(name = "height") @Json(name = "book_image_height") val imageHeight: Int,
    @ColumnInfo(name = "contributor") val contributor: String,
    @ColumnInfo(name = "description") val description: String,
    @ColumnInfo(name = "publisher") val publisher: String,
    @ColumnInfo(name = "rank") val rank: Int,
    @ColumnInfo(name = "rank_last_week") @Json(name = "rank_last_week") val rankLastWeek: Int,
    @ColumnInfo(name = "title") val title: String,
    @ColumnInfo(name = "weeks_on_list") @Json(name = "weeks_on_list") val weeksOnList: Int,
    // 新增外键字段,关联所属List的displayName
    @ColumnInfo(name = "list_display_name") val listDisplayName: String
)

2. 创建关系类,实现一对多关联查询

通过Room的@Relation注解,定义包含Books的List包装类:

data class ListWithBooks(
    @Embedded val list: Lists,
    @Relation(
        parentColumn = "display_name",
        entityColumn = "list_display_name"
    )
    val books: List<Books>
)

3. 编写Dao接口,实现数据操作

在Dao中添加插入和关联查询方法,注意关联查询必须加上@Transaction保证原子性:

@Dao
interface LibraryDao {
    // 插入List,忽略重复数据
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertList(list: Lists)

    // 插入单本Book,忽略重复数据
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertBook(book: Books)

    // 批量插入Books
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertBooks(books: List<Books>)

    // 查询所有包含Books的List
    @Transaction
    @Query("SELECT * FROM lists_table")
    suspend fun getAllListsWithBooks(): List<ListWithBooks>

    // 根据displayName查询指定List及其关联的Books
    @Transaction
    @Query("SELECT * FROM lists_table WHERE display_name = :displayName")
    suspend fun getListWithBooks(displayName: String): ListWithBooks?
}

4. 插入数据的注意事项

插入时需先插入List,再插入关联的Books(避免外键约束报错):

// 示例插入逻辑
suspend fun addListAndBooks(list: Lists, books: List<Books>) {
    // 给每本Book添加上所属List的关联字段
    val linkedBooks = books.map { it.copy(listDisplayName = list.displayName) }
    libraryDao.insertList(list)
    libraryDao.insertBooks(linkedBooks)
}

关键要点

  • 必须遵循数据库范式拆分实体,不能在实体中直接嵌套对象列表
  • 主键选择要符合业务逻辑,避免用可能重复的字段作为单一主键
  • 关联查询必须添加@Transaction,确保查询过程中数据不会发生变更

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 02:51:03