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:
- Room bezpośrednio obsługuje Flow w Kotlinie, co umożliwia pisanie zapytań obserwowanych.
- Room wymaga słowa kluczowego
suspend, aby zapytania jednorazowe DAO były asynchroniczne w przypadku współprogramów Kotlin.
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:
- Dodaj artefakt
androidx.room3:room3-rxjava3do konfiguracji kompilacji. - Dodaj do deklaracji
@Databaselub@Daoadnotację@DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).
Room obsługuje te typy zwracane RxJava 3:
- Zapytania jednorazowe:
Completable,Single<T>iMaybe<T> - Zapytania obserwowane:
Publisher<T>,Flowable<T>, iObservable<T>
LiveData i Gujawa
Room 3.0 obsługuje typy zwracane LiveData i Guava ListenableFuture za pomocą konwerterów:
- LiveData: dodaj artefakt
androidx.room3:room3-livedatai dodaj do bazy danych lub DAO adnotację@DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class). - Gujawa: dodaj artefakt
androidx.room3:room3-guavai 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 lambdasuspend, 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.
- Jeśli konwerter musi przekształcić zapytanie, np. stronicowanie, lambda może przyjmować parametr
- 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>lubList<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.