2

[译] 如何用 Room 处理一对一,一对多,多对多关系?

 2 years ago
source link: http://www.androidchina.net/10544.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
[译] 如何用 Room 处理一对一,一对多,多对多关系? – Android开发中文站
你的位置:Android开发中文站 > 热点资讯 > [译] 如何用 Room 处理一对一,一对多,多对多关系?

原文作者:Florina Muntenescu
原文地址: https://medium.com/androiddevelopers/database-relations-with-room-544ab95e4542
译者:秉心说

译者说:最近在做一款 Rss 阅读器,使用 Room 存储订阅源以及其中的文章,这就是一个典型的 一对多 关系。正好通过此文详细了解 @Relation 注解的使用。

将数据拆分为相关联的表,并以有意义的方式将数据组合在一起 是设计关系型数据库的重要部分。从 Room 2.2 (现已稳定)开始,通过 @Relation 注解,我们支持了表之间所有可能的关系:一对一,一对多,多对多 。

假如我们生活在一个(悲伤的)世界,每个人只能拥有一条狗,并且每条狗也只能有一个主人。这就是一对一关系。为了在关系型数据库中 表示这一关系,我们创建了两张表,Dog 和 Owner 。Dog 表持有 owner id 的引用,Owner 表持有 dog id 的引用。在 Room 中,我们创建这样两个实体类:

@Entity
data class Dog(
    @PrimaryKey val dogId: Long,
    val dogOwnerId: Long,
    val name: String,
    val cuteness: Int,
    val barkVolume: Int,
    val breed: String
)
@Entity
data class Owner(@PrimaryKey val ownerId: Long, val name: String)

我们要在页面上显示所有的狗狗和它们的主人,为此,我们创建了一个数据类

DogAndOwner :
data class DogAndOwner(
    val owner: Owner,
    val dog: Dog
)

要通过 Sqlite 完成此次查询,我们需要:

进行两次查询,先查询出所有的主人,然后在根据主人的 owner id 查询出所有的狗
处理对象映射

SELECT * FROM Owner
SELECT * FROM Dog
    WHERE dogOwnerId IN (ownerId1, ownerId2, …)

通过 Room 来得到 List ,我们不需要自己实现两次查询和对象映射,仅仅通过 @Relation 注解即可。

在上面的例子中,由于 Dog 拥有主人的信息,所有在 dog 变量上添加 @Relation 注解:指定 owner 表中的 ownerId 和 dog 表中的 dogOwnerId 是相对应的。

data class DogAndOwner(
    @Embedded val owner: Owner,
    @Relation(
         parentColumn = "ownerId",
         entityColumn = "dogOwnerId"
    )
    val dog: Dog
)

Dao 可以简化如下:

@Transaction
@Query("SELECT * FROM Owner")
fun getDogsAndOwners(): List<DogAndOwner>

注意:由于 Room 会在后台自动为我们执行这两次查询,所以要添加 @Transaction 注解以保证原子性。

假设一个主人可以拥有多条狗狗 (Yeah !) ,Owner 和 Dog 之间是一对多的关系。之前定义的数据库结构不需要发生任何变化,我们仍然使用之前的表,因为相关联的键已经在表中了。

现在,为了展示主人和他的狗狗们,我们需要创建一个新的数据类:

data class OwnerWithDogs(
    val owner: Owner,
    val dogs: List<Dog>
)

为了避免两次查询,我们给 List 添加 @Relation 注解来定义 Dog 和 Owner 之间的一对多关系。

data class OwnerWithDogs(
     @Embedded val owner: Owner,
     @Relation(
          parentColumn = "ownerId",
          entityColumn = "dogOwnerId"
     )
     val dogs: List<Dog>
)

Dao 是这样的。

@Transaction
@Query("SELECT * FROM Owner")
fun getDogsAndOwners(): List<OwnerWithDogs>

现在假设我们生活在一个完美的世界,每个主人可以拥有多条狗,每条狗也可以有多个主人。要对此关系进行建模,仅仅通过 Dog 表和 Owner 表是不够的。由于一条狗可能有多个主人,所以同一个 dogId 可能需要多条数据,以匹配不同的主人。但是在 Dog 表中,dogId 是主键,我们不能插入多个 id 相同,主人不同的狗狗。为了解决这一问题,我们需要额外创建一个存储 (dogId,ownerId) 的 关联表 (也称为交叉引用表) 。

@Entity(primaryKeys = ["dogId", "ownerId"])
data class DogOwnerCrossRef(
    val dogId: Long,
    val ownerId: Long
)

假设现在仅仅只通过 Sqlite 来所有的主人和他们的狗:List ,我们需要两次查询:获取所有的主人,联表查询 Dog 表和 DogOwnerCrossRef 表。

SELECT * FROM Owner
SELECT
     Dog.dogId AS dogId,
     Dog.dogOwnerId AS dogOwnerId,
     Dog.name AS name,
     _junction.ownerId
FROM
     DogOwnerCrossRef AS _junction
INNER JOIN Dog ON (_junction.dogId = Dog.dogId)

用 Room 实现的话,我们需要更新 OwnerWithDogs 实例类,告诉 Room 要为了获取对应的所有狗狗,要关联表 DogOwnerCrossRef 。通过 Junction 来引用表。

在 Dao 中,通过查询 Owner 来返回正确的数据类。

@Transactionhttps://youtu.be/_aJsh6P00c0
@Query("SELECT * FROM Owner")
fun getOwnersWithDogs(): List<OwnerWithDogs>

高级用法示例

当使用 @Relation 注解时,Room 根据被注解的属性类型来推断使用哪个实体类。例如,到目前为止,我们给 Dog 或 List 添加了注解,这就告诉了 Room 要使用哪个类,要查询哪些字段。

如果我们想返回一个其他对象,例如 Pup,它不是一个实体但是包含了一些字段。我们可以通过 @Relation 注解指定要使用的实体。

data class Pup(
     val name: String,
     val cuteness: Int = 11
)
data class OwnerWithPups(
     @Embedded val owner: Owner,
     @Relation(
          parentColumn = "ownerId",
          entity = Dog::class,
          entityColumn = "dogOwnerId"
     )
     val dogs: List<Pup>
)

如果我们指向返回实体类的指定字段,就需要通过 @Relation 注解的 projection 属性来指定。例如,我们指向获取 OwnerWithDogs 中所有狗狗的名字,因此我们需要返回的是 List 。而 Room 无法推断这些字符串代表的是名字还是品种,所有需要我们通过 projection 指定。

data class OwnerWithDogs(
     @Embedded val owner: Owner,
     @Relation(
           parentColumn = "ownerId",
           entity = Dog::class,
           entityColumn = "dogOwnerId",
           projection = ["name"]
     )
     val dogNames: List<String>
)

如果你想在 dogOwnerId 和 ownerId 之间定义更加严格的关系,独立于你所创建的任何关系,可以在这些字段之间添加 ForeignKey 约束。请记住,SQLite 外键定义索引,并且可以具有级联触发器来更新或删除表中的条目。因此,请根据是否希望在数据库中使用这种功能来决定是否要使用外键。

无论你需要一对一,一对多,还是多对多的支持,Room 都可以通过 @Relation 注释满足你。

作者:秉心说TM
链接:https://juejin.im/post/5e5bdce6e51d452705318c59


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK