由于 SQLite 是关系型数据库,因此您可以在实体之间定义关系。不过,虽然大多数对象关系映射库允许实体对象相互引用,但 Room 明确禁止这样做。如需了解此决定的技术原因,请参阅了解 Room 不允许对象引用的原因。
关系类型
Room 支持以下关系类型:
- 一对一:表示单个实体与另一个单个实体相关的关系。
- 一对多:表示单个实体可与另一种类型的多个实体相关的关系。
- 多对多:表示一种类型的多个实体可与另一种类型的多个实体相关的关系。这通常需要一个关联表。
- 嵌套关系(使用嵌入对象):表示实体包含另一个实体作为字段的关系,并且此嵌套实体可进一步包含其他实体。此关系使用
@Embedded
注解。
在两种方法之间选择
在 Room 中,有两种方法可以定义和查询实体之间的关系。您可以使用以下任一方法:
- 使用包含嵌入对象的中间数据类;或者
- 使用返回类型为 multimap 的关系查询方法。
如果您没有特殊原因必须使用中间数据类,我们建议使用返回类型为 multimap 的方法。如需详细了解此方法,请参阅返回 multimap。
中间数据类方法可避免编写复杂的 SQL 查询,但由于需要额外的中间数据类,代码复杂度也会增加。简而言之,返回类型为 multimap 的方法需要您的 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;
}
使用返回类型为 multimap 的方法
在使用返回类型为 multimap 的方法时,您无需定义任何额外的数据类。您可以根据所需的 map 结构为方法定义multimap 返回类型,并在 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 的新增功能 (Android Dev Summit '19)