在閱讀本文前,需要先了解Room框架的使用,入門可點(diǎn)擊筆者Android Jetpack架構(gòu)組件-Room基本使用文章
一、Room 中的數(shù)據(jù)庫關(guān)系查詢
設(shè)計(jì)一個關(guān)系型數(shù)據(jù)庫很重要的一部分是將數(shù)據(jù)拆分成具有相關(guān)關(guān)系的數(shù)據(jù)表,然后將數(shù)據(jù)以符合這種關(guān)系的邏輯方式整合到一起肚菠。從 Room 2.2 的穩(wěn)定版開始,我們可利用一個 @Relation 注解來支持表之間所有可能出現(xiàn)的關(guān)系: 一對一署鸡、一對多和多對多案糙。
1.1、 一對一關(guān)系
假設(shè)我們生活在一個每個人只能擁有一只狗靴庆,且每只狗只能有一個主人的 “悲慘世界” 中时捌,這就是一對一關(guān)系。如果要以關(guān)系型數(shù)據(jù)庫的方式來反應(yīng)它的話炉抒,我們可以創(chuàng)建兩張表: Dog 表和 Owner 表奢讨,其中 Dog 表通過 owner id 來引用 Owner 表中的數(shù)據(jù),或者 Owner 表通過 dog id 來引用 Dog 表中的數(shù)據(jù)焰薄。
@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)
假設(shè)我們想在一個列表中展示所有的狗和它們的主人拿诸,我們需要創(chuàng)建一個 DogAndOwner 類:
data class DogAndOwner(
val owner: Owner,
val dog: Dog
)
為了在 SQLite 中進(jìn)行查詢,我們需要 1) 運(yùn)行兩個查詢: 一個獲取所有的主人數(shù)據(jù)塞茅,一個獲取所有的狗狗數(shù)據(jù)亩码,2) 根據(jù) owner id 來進(jìn)行數(shù)據(jù)的關(guān)系映射。
SELECT * FROM Owner
SELECT * FROM Dog WHERE dogOwnerId IN (ownerId1, ownerId2, …)
要在 Room 中獲取一個 List<DogAndOwner> 野瘦,我們不需要自己去實(shí)現(xiàn)上面說的查詢和映射描沟,只需要使用 @Relation 注解。
在我們的示例中鞭光,由于 Dog 有了 owner 的信息吏廉,我們給 dog 變量增加 @Relation 注解,指定父級 (這里對應(yīng) Owner) 上的 ownerId 列對應(yīng) dogOwnerId:
data class DogAndOwner(
@Embedded val owner: Owner,
@Relation(
parentColumn = "ownerId",
entityColumn = "dogOwnerId"
)
val dog: Dog
)
現(xiàn)在我們的 Dao 類可被簡化成:
@Transaction
@Query("SELECT * FROM Owner")
fun getDogsAndOwners(): List<DogAndOwner>
注意: 由于 Room 會默默的幫我們運(yùn)行兩個查詢請求惰许,因此需要增加 @Transaction 注解來確保這個行為是原子性的席覆。
Dao
https://developer.android.google.cn/reference/androidx/room/Dao
@Transaction
https://developer.android.google.cn/reference/androidx/room/Transaction.html
1.2、 一對多關(guān)系
再假設(shè)汹买,一個主人可以養(yǎng)多只狗狗佩伤,現(xiàn)在上面的關(guān)系就變成了一對多關(guān)系聊倔。我們之前定義的數(shù)據(jù)庫 schema 并不需要改變,仍然使用同樣的表結(jié)構(gòu)畦戒,因?yàn)樵?“多” 這一方的表中已經(jīng)有了關(guān)聯(lián)鍵》娇猓現(xiàn)在,要展示狗和主人的列表障斋,我們需要創(chuàng)建一個新的類來進(jìn)行建模:
data class OwnerWithDogs(
val owner: Owner,
val dogs: List<Dog>
)
為了避免運(yùn)行兩個獨(dú)立的查詢,我們可以在 Dog 和 Owner 中定義一對多的關(guān)系徐鹤,同樣垃环,還是在 List<Dog> 前增加 @Relation 注解。
data class OwnerWithDogs(
@Embedded val owner: Owner,
@Relation(
parentColumn = "ownerId",
entityColumn = "dogOwnerId"
)
val dogs: List<Dog>
)
現(xiàn)在返敬,Dao 類又變成了這樣:
@Transaction
@Query("SELECT * FROM Owner")
fun getDogsAndOwners(): List<OwnerWithDogs>
1.3遂庄、多對多關(guān)系
現(xiàn)在,繼續(xù)假設(shè)我們生活在一個完美的世界中劲赠,一個人可以擁有多只狗涛目,每只狗可以擁有多個主人。要對這個關(guān)系進(jìn)行映射凛澎,之前的 Dog 和 Owner 表是不夠的霹肝。由于一只狗狗可以有多個主人,我們需要在同一個 dog id 上能夠匹配多個不同的 owner id塑煎。由于 dogId 是 Dog 表的主鍵沫换,我們不能直接在 Dog 表中添加同樣 id 的多條數(shù)據(jù)。為了解決這個問題最铁,我們需要創(chuàng)建一個 associative 表 (也被稱為連接表)讯赏,這個表來存儲 (dogId, ownerId) 的數(shù)據(jù)對。
@Entity(primaryKeys = ["dogId", "ownerId"])
data class DogOwnerCrossRef(
val dogId: Long,
val ownerId: Long
)
如果現(xiàn)在我們想要獲取到所有的狗狗和主人的數(shù)據(jù)冷尉,也就是 List<OwnerWithDogs>漱挎,僅需要編寫兩個 SQLite 查詢,一個獲取到所有的主人數(shù)據(jù)雀哨,另一個獲取 Dog 和 DogOwnerCrossRef 表的連接數(shù)據(jù)磕谅。
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 來實(shí)現(xiàn)這個功能,我們需要更新 OwnerWithDogs 數(shù)據(jù)類震束,并告訴 Room 要使用 DogOwnerCrossRef 這個連接表來獲取 Dogs 數(shù)據(jù)怜庸。我們通過使用 Junction 引用這張表。
data class OwnerWithDogs(
@Embedded val owner: Owner,
@Relation(
parentColumn = "ownerId",
entityColumn = "dogId",
associateBy = Junction(DogOwnerCrossRef::class)
)
val dogs: List<Dog>
)
在我們的 Dao 中垢村,我們需要從 Owners 中選擇并返回正確的數(shù)據(jù)類:
@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)
...
})
五宏榕、結(jié)語
都到這里了拓诸,確定不看看實(shí)際開發(fā)中遇到的Room坑和遷移升級操作?待后續(xù)更新
本文示例代碼已上傳至Jetpack_Component