由于 SQLite 是关系型数据库,因此您可以定义各个实体之间的关系。虽然大多数对象关系映射库都允许实体对象相互引用,但 Room 明确禁止这样做。如需了解此决策背后的技术原因,请参阅了解 Room 为何不允许对象引用。
关系类型
Room 支持以下关系类型:
- 一对一:表示单个实体与另一个单个实体之间的关系。
- 一对多:表示一种关系,其中单个实体可以与多个其他类型的实体相关联。
- 多对多:表示一种关系,其中一种类型的多个实体可以与另一种类型的多个实体相关联。这通常需要一个联接表。
- 使用嵌入式对象的嵌套关系:表示一种关系,其中一个实体包含另一个实体作为属性,而此嵌套实体可以进一步包含其他实体。此示例使用了
@Embedded注解。
选择两种方法之一
在 Room 中,您可以通过两种方式定义和查询实体之间的关系。您可以使用以下任一方法:
- 包含嵌入对象的中间数据类,或
- 具有多重映射返回值类型的关系型查询函数。
如果您没有使用中间数据类的特定原因,我们建议您使用多重映射返回值类型方法。如需详细了解此方法,请参阅返回多重映射。
中间数据类方法可让您避免编写复杂的 SQL 查询,但由于需要额外的数据类,它还可能会导致代码复杂性增加。简而言之,多重映射返回值类型方法需要 SQL 查询完成更多工作;而中间数据类方法需要代码完成更多工作。
使用中间数据类方法
在中间数据类方法中,您可以定义数据类,以便在 Room 实体之间建立关系。此数据类保存一个实体的实例与另一个实体的实例之间的配对(作为嵌入式对象)。然后,查询函数可以返回此数据类的实例,以供您的应用使用。
例如,您可以定义 UserBook 数据类来表示已借阅特定图书的图书馆用户,并定义一个查询函数用于从数据库中检索 UserBook 实例的列表:
@Dao interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user JOIN book ON user.id = book.user_id """ ) fun loadUserAndBookNames(): LiveData<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
使用多重映射返回值类型方法
在多重映射返回值类型方法中,您无需定义任何其他数据类,而是根据所需的映射结构为您的方法定义多重映射返回值类型,并直接在 SQL 查询中定义实体之间的关系。
例如,以下查询函数会返回 User 和 Book 实例的映射,用于表示已借阅特定图书的图书馆用户:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
借助多重映射返回类型,您还可以查询不涉及其他实体的一对一关系。以下查询函数使用 @MapColumn 注解返回 User 与其借阅的图书数量的映射:
@Query( """ SELECT user.*, COUNT(book.id) AS book_count FROM user LEFT JOIN book ON user.id = book.user_id GROUP BY user.id """ ) suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>
创建嵌入式对象
有时,您可能希望在数据库逻辑中将某个实体或数据对象表示为一个紧密的整体,即使该对象包含多个属性也是如此。在这些情况下,请使用 @Embedded 注释将对象分解为表格中的子属性。然后,您可以像查询其他列一样查询嵌入式属性。
例如,您的 User 类可以包含一个 Address 属性,该属性表示 street、city、state 和 postCode 属性的组合。若要在表中单独存储组合列,请使用 @Embedded 注释 User 类中的 Address 属性。以下代码段展示了此设置:
data class Address( val street: String?, val state: String?, val city: String?, @ColumnInfo(name = "post_code") val postCode: Int ) @Entity data class User( @PrimaryKey val id: Int, val firstName: String, @Embedded val address: Address? )
然后,表示 User 对象的表将包含具有以下名称的列:id、firstName、street、state、city 和 post_code。
如果某个实体具有相同类型的多个嵌入式属性,您可以通过设置 prefix 属性确保每个列的唯一性。然后,Room 会将提供的值添加到嵌套对象中每个列名称的开头。