Android Jetpack架構組件-Room數據庫查詢藝術

在閱讀本文前,需要先了解Room框架的使用,入門可點擊筆者Android Jetpack架構組件-Room基本使用文章

一、Room 中的數據庫關系查詢

設計一個關系型數據庫很重要的一部分是將數據拆分成具有相關關系的數據表,然后將數據以符合這種關系的邏輯方式整合到一起。從 Room 2.2 的穩定版開始,我們可利用一個 @Relation 注解來支持表之間所有可能出現的關系: 一對一、一對多和多對多。

1.1、 一對一關系

image.png

假設我們生活在一個每個人只能擁有一只狗,且每只狗只能有一個主人的 “悲慘世界” 中,這就是一對一關系。如果要以關系型數據庫的方式來反應它的話,我們可以創建兩張表: 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、 一對多關系

image.png

再假設,一個主人可以養多只狗狗,現在上面的關系就變成了一對多關系。我們之前定義的數據庫 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、多對多關系

截屏2020-04-0117.54.48.png

現在,繼續假設我們生活在一個完美的世界中,一個人可以擁有多只狗,每只狗可以擁有多個主人。要對這個關系進行映射,之前的 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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容