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