Zapisz asynchroniczne zapytania DAO

Aby zapobiec blokowaniu interfejsu przez zapytania, Room nie obsługuje dostępu do bazy danych w wątku głównym. To ograniczenie oznacza, że zapytania DAO muszą być asynchroniczne. Biblioteka Room zawiera integracje z kilkoma platformami, które umożliwiają wykonywanie zapytań asynchronicznych.

Zapytania DAO dzielą się na 3 kategorie:

  • Zapytania jednorazowe, które wstawiają, aktualizują lub usuwają dane w bazie danych.
  • Zapytania jednorazowe , które odczytują dane z bazy danych tylko raz i zwracają wynik ze stanem bazy danych w danym momencie.
  • Zapytania obserwowane , które odczytują dane z bazy danych za każdym razem, gdy zmieniają się tabele bazowe, i emitują nowe wartości odzwierciedlające te zmiany.

Opcje języka i platformy

Room zapewnia obsługę integracji w celu zapewnienia interoperacyjności z określonymi funkcjami języka i bibliotekami. W tabeli poniżej znajdziesz odpowiednie typy zwracane na podstawie typu zapytania i platformy:

Typ zapytania Funkcje języka Kotlin (natywne) RxJava Gujawa Jetpack Lifecycle*
Jednorazowe zapisywanie Współprogramy (suspend) Single<T>, Maybe<T>, Completable ListenableFuture<T> Nie dotyczy
Jednorazowe odczytywanie Współprogramy (suspend) Single<T>, Maybe<T> ListenableFuture<T> Nie dotyczy
Obserwowane odczytywanie Flow<T> Flowable<T>, Publisher<T>, Observable<T> Nie dotyczy LiveData<T>

W tym przewodniku pokazujemy 3 sposoby korzystania z tych integracji do implementowania asynchronicznych zapytań w DAO.

Kotlin z Flow i współprogramami

Kotlin udostępnia wbudowane funkcje języka, które umożliwiają pisanie asynchronicznych zapytań bez użycia platform innych firm:

Obsługa współprogramów i Flow jest wbudowana bezpośrednio w podstawowe środowisko wykonawcze Room, więc nie są wymagane żadne dodatkowe artefakty.

RxJava dla Kotlin i Java

Room 3.0 obsługuje typy zwracane RxJava 3. Aby używać zwracanych typów RxJava, musisz zarejestrować konwertery zwracanych typów RxJava w bazie danych lub DAO:

  1. Dodaj artefakt androidx.room3:room3-rxjava3 do konfiguracji kompilacji.
  2. Dodaj do deklaracji @Database lub @Dao adnotację @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).

Room obsługuje te typy zwracane RxJava 3:

LiveData i Gujawa

Room 3.0 obsługuje typy zwracane LiveData i Guava ListenableFuture za pomocą konwerterów:

  • LiveData: dodaj artefakt androidx.room3:room3-livedata i dodaj do bazy danych lub DAO adnotację @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class).
  • Gujawa: dodaj artefakt androidx.room3:room3-guava i dodaj do bazy danych lub DAO adnotację @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).

Pisanie asynchronicznych zapytań jednorazowych

Zapytania jednorazowe to operacje na bazie danych, które są wykonywane tylko raz i pobierają stan danych w momencie wykonania. Oto kilka przykładów asynchronicznych zapytań jednorazowych:

@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>
}

Pisanie zapytań obserwowanych

Zapytania obserwowane to operacje odczytu, które emitują nowe wartości za każdym razem, gdy zmieniają się tabele, do których się odwołują. Możesz na przykład użyć tego działania, aby aktualizować wyświetlaną listę elementów w miarę zmian w bazie danych. Oto kilka przykładów zapytań obserwowanych:

@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>>
}

Ręczne śledzenie unieważnienia bazy danych

Gdy musisz ręcznie utworzyć operacje obserwowane na bazie danych, możesz użyć interfejsu API createFlow w InvalidationTracker. Ten interfejs API umożliwia utworzenie Flow, który śledzi modyfikacje określonych tabel i emituje powiadomienie za każdym razem, gdy te tabele się zmienią.

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)
    }
}

Domyślnie zwracany Flow emituje wartość początkową zawierającą wszystkie zarejestrowane tabele, aby uruchomić strumień. Możesz wyłączyć to działanie, ustawiając parametr emitInitialState na false.

Niestandardowe konwertery typów zwracanych DAO

W przypadku typów, które nie są bezpośrednio obsługiwane przez Room ani jego biblioteki rozszerzeń, możesz zdefiniować niestandardowe konwertery zwracanych typów DAO, aby obsługiwać dodatkowe zwracane typy. Aby przekształcić wynik funkcji DAO w typ niestandardowy, dodaj do funkcji konwertera adnotację @DaoReturnTypeConverter.

Możesz na przykład zdefiniować konwerter, który używa androidx.tracing, aby dodawać sekcje śledzenia wokół wykonywania zapytania i monitorować zapytania wrażliwe na wydajność, opakowując wykonanie w niestandardowy typ 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)
    }
}

Aby użyć konwertera, dodaj do bazy danych lub DAO adnotację @DaoReturnTypeConverters:

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

Kontrolowanie inicjowania konwertera typów zwracanych DAO

Zwykle Room obsługuje tworzenie instancji konwerterów zwracanych typów DAO. Jeśli jednak musisz przekazać dodatkowe zależności do klas konwerterów, Twoja aplikacja musi bezpośrednio kontrolować ich inicjowanie. W takim przypadku dodaj do klasy konwertera adnotację @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)
    }
}

Następnie oprócz zadeklarowania klasy konwertera w @DaoReturnTypeConverters użyj funkcji RoomDatabase.Builder.addDaoReturnTypeConverter, aby przekazać instancję klasy konwertera do konstruktora RoomDatabase:

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

Wymagania dotyczące funkcji konwertera

Funkcja @DaoReturnTypeConverter musi spełniać kilka wymagań:

  • Jej ostatnim argumentem musi być parametr funkcyjny, zwykle o nazwie executeAndConvert. Ten parametr to lambda suspend, którą Room generuje w celu wykonania zapytania i przeanalizowania wyniku.
    • Jeśli konwerter musi przekształcić zapytanie, np. stronicowanie, lambda może przyjmować parametr RoomRawQuery.
  • Opcjonalnie przed lambdą może przyjmować te parametry:
    • db: RoomDatabase: uzyskuje dostęp do instancji bazy danych, co jest przydatne do uzyskiwania zakresu współprogramu lub wykonywania dodatkowych operacji.
    • tableNames: Array<String> lub List<String>: podaje nazwy tabel, do których uzyskuje dostęp zapytanie, co jest przydatne w przypadku typów obserwowanych.
    • rawQuery: RoomRawQuery: podaje instancję zapytania w czasie działania.
    • inTransaction: Boolean: wskazuje, czy zapytanie jest wykonywane w ramach transakcji.