在阅读本文前,需要先了解Room框架的使用,入门可点击笔者Android Jetpack架构组件-Room基本使用文章
一、Room 中的数据库关系查询
设计一个关系型数据库很重要的一部分是将数据拆分成具有相关关系的数据表,然后将数据以符合这种关系的逻辑方式整合到一起。从 Room 2.2 的稳定版开始,我们可利用一个 @Relation 注解来支持表之间所有可能出现的关系: 一对一、一对多和多对多。
1.1、 一对一关系
假设我们生活在一个每个人只能拥有一只狗,且每只狗只能有一个主人的 “悲惨世界” 中,这就是一对一关系。如果要以关系型数据库的方式来反应它的话,我们可以创建两张表: Dog 表和 Owner 表,其中 Dog 表通过 owner id 来引用 Owner 表中的数据,或者 Owner 表通过 dog id 来引用 Dog 表中的数据。
@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 中进行查询,我们需要 1) 运行两个查询: 一个获取所有的主人数据,一个获取所有的狗狗数据,2) 根据 owner id 来进行数据的关系映射。
SELECT * FROM Owner
SELECT * FROM Dog WHERE dogOwnerId IN (ownerId1, ownerId2, …)
要在 Room 中获取一个 List<DogAndOwner> ,我们不需要自己去实现上面说的查询和映射,只需要使用 @Relation 注解。
在我们的示例中,由于 Dog 有了 owner 的信息,我们给 dog 变量增加 @Relation 注解,指定父级 (这里对应 Owner) 上的 ownerId 列对应 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 注解来确保这个行为是原子性的。
Dao
https://developer.android.google.cn/reference/androidx/room/Dao
@Transaction
https://developer.android.google.cn/reference/androidx/room/Transaction.html
1.2、 一对多关系
再假设,一个主人可以养多只狗狗,现在上面的关系就变成了一对多关系。我们之前定义的数据库 schema 并不需要改变,仍然使用同样的表结构,因为在 “多” 这一方的表中已经有了关联键。现在,要展示狗和主人的列表,我们需要创建一个新的类来进行建模:
data class OwnerWithDogs(
val owner: Owner,
val dogs: List<Dog>
)
为了避免运行两个独立的查询,我们可以在 Dog 和 Owner 中定义一对多的关系,同样,还是在 List<Dog> 前增加 @Relation 注解。
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>
1.3、多对多关系
现在,继续假设我们生活在一个完美的世界中,一个人可以拥有多只狗,每只狗可以拥有多个主人。要对这个关系进行映射,之前的 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
)
如果现在我们想要获取到所有的狗狗和主人的数据,也就是 List<OwnerWithDogs>,仅需要编写两个 SQLite 查询,一个获取到所有的主人数据,另一个获取 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)
WHERE _junction.ownerId IN (ownerId1, ownerId2, …)
要通过 Room 来实现这个功能,我们需要更新 OwnerWithDogs 数据类,并告诉 Room 要使用 DogOwnerCrossRef 这个连接表来获取 Dogs 数据。我们通过使用 Junction 引用这张表。
data class OwnerWithDogs(
@Embedded val owner: Owner,
@Relation(
parentColumn = "ownerId",
entityColumn = "dogId",
associateBy = Junction(DogOwnerCrossRef::class)
)
val dogs: List<Dog>
)
在我们的 Dao 中,我们需要从 Owners 中选择并返回正确的数据类:
@Transaction
@Query("SELECT * FROM Owner")
fun getOwnersWithDogs(): List<OwnerWithDogs>
二、配合Rxjava的使用
重新定义查询User的Dao,如下所示:注意返回类型
@Transaction
@Query("SELECT * FROM Cheese")
fun findAll(): Flowable<List<CheeseAndUser>>
在Activity/Fragment中的使用
AppDatabase.get(this).cheeseAndUserDao().findAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { t -> Log.d("DATA", "Rxjava:" + t?.size) }
三、配合LiveData的使用
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun queryUsers(): LiveData<List<User>>
}
在Activity/Fragment中的使用
四、配合Paging框架的使用
@Dao
interface UserDao {
@Query("select * from user order by name ")
fun findAllUser(): DataSource.Factory<Int, User>
在ViewModel中的使用
class CheeseViewModel(app: Application) : AndroidViewModel(app) {
val dao = AppDb.get(app).userDao()
val allUser = dao.findAllCheese().toLiveData(
Config(
pageSize = 30,
enablePlaceholders = true,
maxSize = 200
)
)
}
在Activity/Fragment中的使用
allUser.allCheese.observe(this, Observer {
// adapter.submitList(it)
...
})
五、结语
都到这里了,确定不看看实际开发中遇到的Room坑和迁移升级操作?待后续更新
本文示例代码已上传至Jetpack_Component