sqlite表与表之间的关系_Room中的数据库关系
群雄逐鹿
设计⼀个关系型数据库很重要的⼀部分是将数据拆分成具有相关关系的数据表,然后将数据以符合这种关系的逻辑⽅式整合到⼀起。从Room 2.2 的稳定版开始,我们可利⽤⼀个 @Relation 注解来⽀持表之间所有可能出现的关系: ⼀对⼀、⼀对多和多对多。
Room 2.2
@Relation
⼀对⼀关系
⼀对⼀关系
假设我们⽣活在⼀个每个⼈只能拥有⼀只狗,且每只狗只能有⼀个主⼈的 “悲惨世界” 中,这就是⼀对⼀关系。如果要以关系型数据库的
⽅式来反应它的话,我们可以创建两张表: Dog 表和 Owner 表,其中 Dog 表通过 owner id 来引⽤ Owner 表中的数据,或者 Owner 表
通过 dog id 来引⽤ Dog 表中的数据。
安徒生童话故事全集@Entitydata class Dog( @PrimaryKey val dogId: Long, val dogOwnerId: Long, val name: String, val cuteness: Int, val barkVolume: Int, val bree
假设我们想在⼀个列表中展⽰所有的狗和它们的主⼈,我们需要创建⼀个 DogAndOwner 类:
data class DogAndOwner( val owner: Owner, val dog: Dog)
为了在 SQLite 中进⾏查询,我们需要 1) 运⾏两个查询: ⼀个获取所有的主⼈数据,⼀个获取所有的狗狗数据,2) 根据 owner id 来进⾏
数据的关系映射。
SELECT * FROM OwnerSELECT * FROM Dog WHERE dogOwnerId IN (ownerId1, ownerId2, …)
要在 Room 中获取⼀个 List ,我们不需要⾃⼰去实现上⾯说的查询和映射,只需要使⽤ @Relation 注解。 在我们的⽰例中,由于 Dog
有了 owner 的信息,我们给 dog 变量增加 @Relation 注解,指定⽗级 (这⾥对应 Owner ) 上的 ownerId 列对应 dogOwnerId :
data class DogAndOwner( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entityColumn = "dogOwnerId" ) val do
现在我们的 Dao 类可被简化成:
@Transaction@Query("SELECT * FROM Owner")fun getDogsAndOwners(): List
春光乍泄萌动
注意: 由于 Room 会默默的帮我们运⾏两个查询请求,因此需要增加 @Transaction 注解来确保这个⾏为是原⼦性的。
le/reference/androidx/room/Dao
@le/reference/androidx/room/Transaction.html蒜香
⼀对多关系
⼀对多关系 再假设,⼀个主⼈可以养多只狗狗,现在上⾯的关系就变成了⼀对多关系。我们之前定义的数据库 schema 并不需要改变,仍
然使⽤同样的表结构,因为在 “多” 这⼀⽅的表中已经有了关联键。 现在,要展⽰狗和主⼈的列表,我们需要创建⼀个新的类来进⾏建模: data class OwnerWithDogs( val owner: Owner, val dogs: List)
为了避免运⾏两个独⽴的查询,我们可以在 Dog 和 Owner 中定义⼀对多的关系,同样,还是在 List 前增加 @Relation 注解。
data class OwnerWithDogs( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entityColumn = "dogOwnerId" )
现在, Dao 类⼜变成了这样:
@Transaction@Query("SELECT * FROM Owner")fun getDogsAndOwners(): List
多对多关系
多对多关系
现在,继续假设我们⽣活在⼀个完美的世界中,⼀个⼈可以拥有多只狗,每只狗可以拥有多个主⼈。
要对这个关系进⾏映射,之前的 Dog
蓝色的英文怎么写
和 Owner 表是不够的。由于⼀只狗狗可以有多个主⼈,我们需要在同⼀个 dog id 上能够匹配多个不同的 owner id。由于 dogId 是 Dog
表的主键,我们不能直接在 Dog 表中添加同样 id 的多条数据。为了解决这个问题,我们需要创建⼀个 associative 表 (也被称为连接表),这个表来存储 ( dogId , ownerId ) 的数据对。
@Entity(primaryKeys = ["dogId", "ownerId"])data class DogOwnerCrossRef( val dogId: Long, val ownerId: Long)
associative
如果现在我们想要获取到所有的狗狗和主⼈的数据,也就是 List ,仅需要编写两个 SQLite 查询,⼀个获取到所有的主⼈数据,另⼀个获
取 Dog 和 DogOwnerCrossRef 表的连接数据。
SELECT * FROM OwnerSELECT Dog.dogId AS dogId, Dog.dogOwnerId AS dogOwnerId, Dog.name AS name, _junction.ownerIdFROM DogO
要通过 Room 来实现这个功能,我们需要更新 OwnerWithDogs 数据类,并告诉 Room 要使⽤ DogOwnerCrossRef 这个连接表来获取Dogs 数据。我们通过使⽤ Junction 引⽤这张表。
data class OwnerWithDogs( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entityColumn = "dogId", associateB Junction
在我们的 Dao 中,我们需要从 Owners 中选择并返回正确的数据类:
@Transaction@Query("SELECT * FROM Owner")fun getOwnersWithDogs(): List
更⾼阶的数据库关系⽤例
当使⽤ @Relation 注解时,Room 会默认从所修饰的属性类型推断出要使⽤的数据库实体。例如,到⽬前为⽌我们⽤ @Relation 修饰了Dog (或者是 List ),Room 就会知道如何去对该类进⾏建模,以及知道要查询的到底是哪⼀⾏数据。 如果您想让该查询返回⼀个不同的类,⽐如 Pup 这样不是⼀个数据库实体但是包含了⼀些字段的对象。我们可以在 @Relation 注解中指定要使⽤的数据库实体:
data class Pup( val name: String, val cuteness: Int = 11)data class OwnerWithPups( @Embedded val owner: Owner, @Relation( parentCo
如果我们只想从数据库实体中返回特定的列,您需要通过在 @Relation 中的 projection 属性中定义要返回哪些列。例如,假如我们只想获取 OwnerWithDogs 数据类中所有狗的名字,由于我们需要⽤到 List ,Room 不能推断出这些字符串是对应于狗的品种呢还是狗的名
字,因此我们需要在 projection 属性中指名。
data class OwnerWithDogs( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entity = Dog::class, entityColu
如果您想在 dogOwnerId 和 ownerId 中定义更严格的关系,⽽不管您所创建的是什么,您可以通过在字段中使⽤ ForeignKey 来做到。记住, SQLite 中的外键 会创建索引,并且会在更新或者删除表中数据时做级联操作。因此您要根据实际情况来判断是否使⽤外键功能。
le/reference/androidx/room/ForeignKey.html
SQLite 中的外键sqlite/foreignkeys.html
不管您是要使⽤⼀对⼀,⼀对多还是多对多关系,Room 都会为您提供 @Relation 注解来解决问题。您可以在我们的 Android Dev
Summit ’19 的⼀个演讲中了解有关 Room 2.2 的更多新功能:
@le/reference/androidx/room/Relation.html
Room 2.2 的更多新功能le/jetpack/androidx/releas/room#version_220_3
点击屏末 | 阅读原⽂ | 进⼀步了解 Room
想了解更多 Android 内容?
在公众号⾸页发送关键词 "Android",获取相关历史技术⽂章;
路由器账号
在公众号⾸页发送关键词 "ADS",获取开发者峰会演讲中⽂字幕视频;
还有更多疑惑?欢迎点击菜单 "联系我们" 反馈您在开发过程中遇到的问题。推荐阅读
君子以自强不息
我的高中