Asynchrone DAO-Abfragen schreiben

Damit Abfragen die Benutzeroberfläche nicht blockieren, unterstützt Room den Datenbankzugriff im Hauptthread nicht. Daher müssen Sie Ihre DAO-Abfragen asynchron ausführen. Die Room-Bibliothek bietet Integrationen mit mehreren Frameworks, um die asynchrone Abfrageausführung zu ermöglichen.

DAO-Abfragen lassen sich in drei Kategorien unterteilen:

  • One-Shot-Schreibabfragen, mit denen Daten in die Datenbank eingefügt, aktualisiert oder gelöscht werden.
  • One-Shot-Leseabfragen , mit denen Daten nur einmal aus der Datenbank gelesen werden und ein Ergebnis mit dem Snapshot der Datenbank zu diesem Zeitpunkt zurückgegeben wird.
  • Beobachtbare Leseabfragen , mit denen Daten jedes Mal aus der Datenbank gelesen werden, wenn sich die zugrunde liegenden Datenbanktabellen ändern. Außerdem werden neue Werte ausgegeben, um diese Änderungen widerzuspiegeln.

Sprach- und Framework-Optionen

Room bietet Integrationsunterstützung für die Interoperabilität mit bestimmten Sprachfunktionen und Bibliotheken. In der folgenden Tabelle sind die anwendbaren Rückgabetypen basierend auf dem Abfragetyp und dem Framework aufgeführt:

Abfragetyp Kotlin-Sprachfunktionen (nativ) RxJava Guava Jetpack Lifecycle*
One-Shot-Schreibabfrage Koroutinen (suspend) Single<T>, Maybe<T>, Completable ListenableFuture<T>
One-Shot-Leseabfrage Koroutinen (suspend) Single<T>, Maybe<T> ListenableFuture<T>
Beobachtbare Leseabfrage Flow<T> Flowable<T>, Publisher<T>, Observable<T> LiveData<T>

In diesem Leitfaden werden drei Möglichkeiten gezeigt, wie Sie diese Integrationen verwenden können, um asynchrone Abfragen in Ihren DAOs zu implementieren.

Kotlin mit Flow und Koroutinen

Kotlin bietet integrierte Sprachfunktionen, mit denen Sie asynchrone Abfragen ohne Frameworks von Drittanbietern schreiben können:

  • Room unterstützt direkt den Flow von Kotlin, um beobachtbare Abfragen zu schreiben.
  • Room erfordert das suspend Schlüsselwort, um Ihre One-Shot-DAO-Abfragen asynchron mit Kotlin-Koroutinen auszuführen.

Die Unterstützung für Koroutinen und Flow ist direkt in die Room-Laufzeitumgebung integriert, sodass keine zusätzlichen Artefakte erforderlich sind.

RxJava für Kotlin und Java

Room 3.0 unterstützt RxJava 3-Rückgabetypen. Wenn Sie RxJava-Rückgabetypen verwenden möchten, müssen Sie die RxJava-Rückgabetypkonverter in Ihrer Datenbank oder Ihrem DAO registrieren:

  1. Fügen Sie das Artefakt androidx.room3:room3-rxjava3 in Ihre Build-Konfiguration ein.
  2. Markieren Sie Ihre @Database oder @Dao Deklaration mit @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).

Room unterstützt die folgenden RxJava 3-Rückgabetypen:

LiveData und Guava

Room 3.0 unterstützt LiveData- und Guava-ListenableFuture-Rückgabetypen mithilfe von Konvertern:

  • LiveData: Fügen Sie das Artefakt androidx.room3:room3-livedata ein und markieren Sie Ihre Datenbank oder Ihr DAO mit @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class).
  • Guava: Fügen Sie das Artefakt androidx.room3:room3-guava ein und markieren Sie Ihre Datenbank oder Ihr DAO mit @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).

Asynchrone One-Shot-Abfragen schreiben

One-Shot-Abfragen sind Datenbankvorgänge, die nur einmal ausgeführt werden und zum Zeitpunkt der Ausführung einen Snapshot der Daten erstellen. Hier sind einige Beispiele für asynchrone One-Shot-Abfragen:

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

Beobachtbare Abfragen schreiben

Beobachtbare Abfragen sind Lesevorgänge, die neue Werte ausgeben, wenn sich die referenzierten Tabellen ändern. Sie können dieses Verhalten beispielsweise verwenden, um eine angezeigte Liste von Elementen zu aktualisieren, wenn sich die Datenbank ändert. Hier sind einige Beispiele für beobachtbare Abfragen:

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

Datenbankinvalidierung manuell verfolgen

Wenn Sie beobachtbare Datenbankvorgänge manuell erstellen müssen, können Sie die createFlow API von InvalidationTracker verwenden. Mit dieser API können Sie einen Flow erstellen, der Änderungen an bestimmten Tabellen verfolgt und eine Benachrichtigung ausgibt, wenn sich diese Tabellen ändern.

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

Standardmäßig gibt der zurückgegebene Flow einen Anfangswert mit allen registrierten Tabellen aus, um den Stream zu starten. Sie können dieses Verhalten deaktivieren, indem Sie den Parameter emitInitialState auf false setzen.

Benutzerdefinierte DAO-Rückgabetypkonverter

Für Typen, die von Room oder den zugehörigen Erweiterungsbibliotheken nicht direkt unterstützt werden, können Sie benutzerdefinierte DAO-Rückgabetypkonverter definieren, um zusätzliche Rückgabetypen zu unterstützen. Wenn Sie das Ergebnis einer DAO-Funktion in Ihren benutzerdefinierten Typ umwandeln möchten, markieren Sie eine Konverterfunktion mit @DaoReturnTypeConverter.

Sie können beispielsweise einen Konverter definieren, der androidx.tracing verwendet, um Trace-Abschnitte um die Ausführung einer Abfrage hinzuzufügen. So können Sie leistungsempfindliche Abfragen überwachen, indem Sie die Ausführung in einen benutzerdefinierten TracedQuery-Typ einbinden:

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

Wenn Sie den Konverter verwenden möchten, markieren Sie Ihre Datenbank oder Ihr DAO mit @DaoReturnTypeConverters:

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

Initialisierung des DAO-Rückgabetypkonverters steuern

Normalerweise übernimmt Room die Instanziierung von DAO-Rückgabetypkonvertern. Wenn Sie jedoch zusätzliche Abhängigkeiten an Ihre Konverterklassen übergeben müssen, muss Ihre App die Initialisierung direkt steuern. Markieren Sie in diesem Fall Ihre Konverterklasse mit @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)
    }
}

Zusätzlich zur Deklaration Ihrer Konverterklasse in @DaoReturnTypeConverters, verwenden Sie die RoomDatabase.Builder.addDaoReturnTypeConverter Funktion, um eine Instanz Ihrer Konverterklasse an den RoomDatabase Builder zu übergeben:

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

Anforderungen an Konverterfunktionen

Eine @DaoReturnTypeConverter-Funktion muss mehrere Anforderungen erfüllen:

  • Sie muss einen funktionalen Parameter als letztes Argument haben, der normalerweise executeAndConvert heißt. Dieser Parameter ist ein suspend-Lambda, das von Room generiert wird, um die Abfrage auszuführen und das Ergebnis zu parsen.
    • Wenn der Konverter die Abfrage transformieren muss, z. B. Paging, kann das Lambda einen RoomRawQuery-Parameter verwenden.
  • Optional können vor dem Lambda die folgenden Parameter akzeptiert werden:
    • db: RoomDatabase: Greift auf die Datenbankinstanz zu, was nützlich ist, um den Koroutinenbereich abzurufen oder zusätzliche Vorgänge auszuführen.
    • tableNames: Array<String> oder List<String>: Gibt die Namen der Tabellen an, auf die von der Abfrage zugegriffen wird. Dies ist für beobachtbare Typen nützlich.
    • rawQuery: RoomRawQuery: Gibt die Laufzeitinstanz der Abfrage an.
    • inTransaction: Boolean: Gibt an, ob die Abfrage innerhalb einer Transaktion ausgeführt wird.