寫入非同步 DAO 查詢

為防止查詢封鎖 UI,Room 不支援在主執行緒上存取資料庫。這項限制表示您必須將 DAO 查詢設為非同步。Room 程式庫包含多種架構的整合,以提供非同步查詢執行作業。

DAO 查詢分為三個類別:

  • 「單一寫入」查詢,可在資料庫中插入、更新或刪除資料。
  • 「單一讀取」查詢,只會從資料庫讀取資料一次,並傳回當時資料庫的快照結果。
  • 「可觀測讀取」查詢,可在基礎資料庫資料表變更時從資料庫讀取資料,並傳回新的值以反映這些變更。

語言和架構選項

Room 提供整合支援,以實現與特定語言功能和資料庫的互通性。下表顯示了以查詢類型和架構為基礎的適用傳回類型:

查詢類型 Kotlin 語言功能 (原生) RxJava Guava Jetpack Lifecycle*
單一寫入 協同程式 (suspend) Single<T>Maybe<T>Completable ListenableFuture<T> 不適用
單一讀取 協同程式 (suspend) Single<T>Maybe<T> ListenableFuture<T> 不適用
可觀測讀取 Flow<T> Flowable<T>Publisher<T>Observable<T> 不適用 LiveData<T>

本指南說明利用整合功能在 DAO 中實作非同步查詢的三種方法。

Kotlin 搭配 Flow 和協同程式

Kotlin 提供內建語言功能,可讓您在不使用第三方架構的情況下,寫入非同步查詢:

  • Room 直接支援 Kotlin 的 Flow,可撰寫可觀測的查詢。
  • Room 需要 suspend 關鍵字,才能使用 Kotlin 協同程式進行非同步 DAO 單次查詢。

協同程式和 Flow 支援功能直接內建於核心 Room 執行階段,因此不需要額外構件。

適用於 Kotlin 和 Java 的 RxJava

Room 3.0 支援 RxJava 3 傳回類型。如要使用 RxJava 傳回類型,您必須在資料庫或 DAO 中註冊 RxJava 傳回類型轉換器:

  1. 在建構設定中加入 androidx.room3:room3-rxjava3 構件。
  2. 使用 @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class)@Database@Dao 宣告加上註解。

Room 支援下列 RxJava 3 傳回類型:

LiveData 和 Guava

Room 3.0 支援使用轉換器,傳回 LiveData 和 Guava ListenableFuture 類型:

  • LiveData:加入 androidx.room3:room3-livedata 構件,並使用 @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class) 註解資料庫或 DAO。
  • Guava:加入 androidx.room3:room3-guava 構件,並使用 @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class) 註解資料庫或 DAO。

撰寫非同步單一查詢

單一查詢是指僅執行一次、擷取執行當下資料快照的資料庫作業。以下列舉幾種非同步單一查詢:

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun loadUserById(id: Int): User

    @Query("SELECT * from user WHERE region IN (:regions)")
    suspend fun loadUsersByRegion(regions: List<String>): List<User>
}

撰寫可觀測查詢

可觀測查詢屬於讀取作業,在參照的資料表發生變更時,即會觸發新的值。舉例來說,您可以使用這項行為,在資料庫變更時,讓顯示的項目清單保持在最新狀態。以下列舉幾個可觀測的查詢:

@Dao
interface ObservableUserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    fun loadUserById(id: Int): Flow<User>

    @Query("SELECT * from user WHERE region IN (:regions)")
    fun loadUsersByRegion(regions: List<String>): Flow<List<User>>
}

手動追蹤資料庫失效

需要手動建構可觀測的資料庫作業時,可以使用 InvalidationTrackercreateFlow API。這個 API 可讓您建立 Flow,追蹤特定資料表的修改內容,並在這些資料表變更時發出通知。

fun getArtistTours(db: RoomDatabase, from: Date, to: Date): Flow<Map<Artist, TourState>> {
    return db.invalidationTracker.createFlow("Artist").map { _ ->
        val artists = artistsDao.getAllArtists()
        val tours = tourService.fetchStates(artists.map { it.id })
        associateTours(artists, tours, from, to)
    }
}

根據預設,傳回的 Flow 會發出包含所有已註冊資料表的初始值,以啟動串流。如要停用這項行為,請將 emitInitialState 參數設為 false

自訂 DAO 傳回類型轉換器

對於 Room 或其擴充功能程式庫未直接支援的型別,您可以定義自訂 DAO 傳回類型轉換器,以支援其他傳回類型。如要將 DAO 函式的結果轉換為自訂型別,請使用 @DaoReturnTypeConverter 註解標註轉換器函式。

舉例來說,您可以定義使用 androidx.tracing 的轉換器,在查詢執行作業周圍新增追蹤區段,方法是將執行作業包裝在自訂 TracedQuery 型別中,藉此監控對效能要求嚴格的查詢:

class TracedQuery<T>(val result: T)

object TracingDaoReturnTypeConverter {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

如要使用轉換器,請將 @DaoReturnTypeConverters 註解加到資料庫或 DAO:

@Dao
@DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class)
interface MusicDao {
    @Query("SELECT * FROM Song")
    suspend fun getAllSongs(): TracedQuery<List<Song>>
}

控制 DAO 傳回類型轉換器初始化

一般而言,Room 會處理 DAO 傳回類型轉換器的例項建立作業。不過,如果您必須將其他依附元件傳遞至轉換器類別,應用程式就必須直接控管這些依附元件的初始化作業。如果是,請將 @ProvidedDaoReturnTypeConverter 註解加到轉換器類別:

@ProvidedDaoReturnTypeConverter
class TracingDaoReturnTypeConverter(val tracer: Tracer) {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = tracer.trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

接著,除了在 @DaoReturnTypeConverters 中宣告轉換器類別之外,請使用 RoomDatabase.Builder.addDaoReturnTypeConverter 函式,將轉換器類別的例項傳遞至 RoomDatabase 建構工具:

val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name")
    .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance))
    .build()

轉換函式規定

@DaoReturnTypeConverter 函式必須符合下列幾項規定:

  • 最後一個引數必須是函式參數,通常命名為 executeAndConvert。這個參數是 Room 產生的 suspend lambda,用於執行查詢及剖析結果。
    • 如果轉換器需要轉換查詢 (例如分頁),lambda 可以採用 RoomRawQuery 參數。
  • 這個函式可選擇性地接受 Lambda 前的下列參數:
    • db: RoomDatabase:存取資料庫執行個體,有助於取得協同程式範圍或執行其他作業。
    • tableNames: Array<String>List<String>:提供查詢存取的表格名稱,適用於可觀察的型別。
    • rawQuery: RoomRawQuery:提供查詢的執行階段例項。
    • inTransaction: Boolean:指出查詢是否在交易中執行。