Android Jetpack架構(gòu)組件-Room數(shù)據(jù)庫查詢藝術(shù)

在閱讀本文前,需要先了解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)系

image.png

假設(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)系

image.png

再假設(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)系

截屏2020-04-0117.54.48.png

現(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末麻昼,一起剝皮案震驚了整個濱河市奠支,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抚芦,老刑警劉巖倍谜,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叉抡,居然都是意外死亡尔崔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門褥民,熙熙樓的掌柜王于貴愁眉苦臉地迎上來季春,“玉大人,你說我怎么就攤上這事消返≡嘏” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵撵颊,是天一觀的道長宇攻。 經(jīng)常有香客問我,道長秦驯,這世上最難降的妖魔是什么尺碰? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮译隘,結(jié)果婚禮上亲桥,老公的妹妹穿的比我還像新娘。我一直安慰自己固耘,他們只是感情好题篷,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著厅目,像睡著了一般番枚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上损敷,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天葫笼,我揣著相機(jī)與錄音,去河邊找鬼拗馒。 笑死路星,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诱桂。 我是一名探鬼主播洋丐,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼呈昔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了友绝?” 一聲冷哼從身側(cè)響起堤尾,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎迁客,沒想到半個月后郭宝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哲泊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年剩蟀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片切威。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖丙号,靈堂內(nèi)的尸體忽然破棺而出先朦,到底是詐尸還是另有隱情,我是刑警寧澤犬缨,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布喳魏,位于F島的核電站,受9級特大地震影響怀薛,放射性物質(zhì)發(fā)生泄漏刺彩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一枝恋、第九天 我趴在偏房一處隱蔽的房頂上張望创倔。 院中可真熱鬧,春花似錦焚碌、人聲如沸畦攘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽知押。三九已至,卻和暖如春鹃骂,著一層夾襖步出監(jiān)牢的瞬間台盯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工畏线, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留静盅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓象踊,卻偏偏與公主長得像温亲,于是被迫代替她去往敵國和親棚壁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容