Wenn Sie die Room-Persistenzbibliothek zum Speichern der Daten Ihrer App verwenden, interagieren Sie mit den gespeicherten Daten, indem Sie Datenzugriffsobjekte (Data Access Objects, DAOs) definieren. Jedes DAO enthält Funktionen, die einen abstrakten Zugriff auf die Datenbank Ihrer App ermöglichen. Zur Kompilierzeit generiert Room automatisch Implementierungen der von Ihnen definierten DAOs.
Wenn Sie für den Zugriff auf die Datenbank Ihrer App DAOs anstelle von Abfrage-Buildern oder direkten Abfragen verwenden, können Sie die Trennung von Zuständigkeiten beibehalten, ein wichtiges Architektur prinzip. Mit DAOs können Sie den Datenbankzugriff auch simulieren, wenn Sie Ihre App testen.
Aufbau eines DAO
Sie können jedes DAO entweder als Schnittstelle oder als abstrakte Klasse definieren. Für einfache Anwendungsfälle wird in der Regel eine Schnittstelle verwendet. In beiden Fällen müssen Sie Ihre DAOs immer
mit @Dao annotieren. DAOs haben keine Properties, definieren aber eine oder mehrere Funktionen für die Interaktion mit den Daten in der Datenbank Ihrer App.
Der folgende Code ist ein Beispiel für ein DAO, das Funktionen zum Einfügen, Löschen und Auswählen von User-Objekten in einer Room-Datenbank definiert:
@Dao interface UserDao { @Insert suspend fun insertAll(vararg users: User) @Delete suspend fun delete(user: User) @Query("SELECT * FROM user") suspend fun getAll(): List<User> }
Es gibt zwei Arten von DAO-Funktionen, die Datenbankinteraktionen definieren:
- Komfortfunktionen, mit denen Sie Zeilen in Ihrer Datenbank einfügen, aktualisieren und löschen können, ohne SQL-Code schreiben zu müssen.
- Abfragefunktionen, mit denen Sie Ihre eigene SQL-Abfrage schreiben können, um mit der Datenbank zu interagieren.
In den folgenden Abschnitten wird gezeigt, wie Sie beide Arten von DAO-Funktionen verwenden, um die Datenbankinteraktionen zu definieren, die Ihre App benötigt.
Komfortfunktionen
Room bietet Komfortannotationen zum Definieren von Funktionen, mit denen Einfügungen, Aktualisierungen und Löschungen ausgeführt werden können, ohne dass Sie eine SQL-Anweisung schreiben müssen.
Wenn Sie komplexere Einfügungen, Aktualisierungen oder Löschungen definieren oder Sie Daten in der Datenbank abfragen möchten, verwenden Sie stattdessen eine Abfragefunktion.
Einfügen
Mit der @Insert Annotation können Sie Funktionen definieren, die ihre
Parameter in die entsprechende Tabelle in der Datenbank einfügen. Der folgende Code zeigt Beispiele für gültige @Insert-Funktionen, die ein oder mehrere User-Objekte in die Datenbank einfügen:
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUsers(vararg users: User) @Insert suspend fun insertBothUsers(user1: User, user2: User) @Insert suspend fun insertUsersAndFriends(user: User, friends: List<User>) }
Jeder Parameter für eine @Insert-Funktion muss entweder eine Instanz einer Room
Datenentitätsklasse, die mit @Entity annotiert ist, oder eine Sammlung von Instanzen der Datenentitätsklasse
sein. Wenn eine @Insert-Funktion aufgerufen wird, fügt Room jede übergebene Entitätsinstanz in die entsprechende Datenbanktabelle ein.
Wenn die @Insert-Funktion einen einzelnen Parameter empfängt, kann sie einen Long-Wert zurückgeben, der die neue rowId für das eingefügte Element ist. Wenn der Parameter ein Array oder eine Sammlung ist, sollte stattdessen ein Array oder eine Sammlung von Long-Werten zurückgegeben werden, wobei jeder Wert die rowId für eines der eingefügten Elemente ist.
Weitere Informationen zum Zurückgeben von rowId Werten finden Sie in der Referenz
dokumentation für die @Insert Annotation und in der SQLite-Dokumentation
zu rowid-Tabellen.
Aktualisieren
Mit der @Update Annotation können Sie Funktionen definieren, die bestimmte
Zeilen in einer Datenbanktabelle aktualisieren. Wie @Insert-Funktionen akzeptieren auch @Update-Funktionen Datenentitätsinstanzen als Parameter. Der folgende Code zeigt ein Beispiel für eine @Update-Funktion, die versucht, ein oder mehrere User-Objekte in der Datenbank zu aktualisieren:
@Dao interface UserDao { @Update suspend fun updateUsers(vararg users: User) }
Room verwendet den Primärschlüssel, um Entitätsinstanzen in Argumenten mit Zeilen in der Datenbank abzugleichen. Wenn keine Zeile mit demselben Primärschlüssel vorhanden ist, nimmt Room keine Änderungen vor.
Eine @Update-Funktion kann optional einen Int-Wert zurückgeben, der die Anzahl der erfolgreich aktualisierten Zeilen angibt.
Löschen
Mit der @Delete Annotation können Sie
Funktionen definieren, die bestimmte Zeilen aus einer Datenbanktabelle löschen. Wie @Insert-Funktionen akzeptieren auch @Delete-Funktionen Datenentitätsinstanzen als Parameter. Der folgende Code zeigt ein Beispiel für eine @Delete-Funktion, die versucht, ein oder mehrere User-Objekte aus der Datenbank zu löschen:
@Dao interface UserDao { @Delete suspend fun deleteUsers(vararg users: User) }
Room verwendet den Primärschlüssel, um Entitätsinstanzen in Argumenten mit Zeilen in der Datenbank abzugleichen. Wenn keine Zeile mit demselben Primärschlüssel vorhanden ist, nimmt Room keine Änderungen vor.
Eine @Delete-Funktion kann optional einen Int-Wert zurückgeben, der die Anzahl der erfolgreich gelöschten Zeilen angibt.
Upsert
Die @Upsert Annotation ermöglicht es Ihnen,
Funktionen zu definieren, die Entitätsinstanzen einfügen, wenn keine übereinstimmende Zeile vorhanden ist, oder
sie zu aktualisieren, wenn bereits eine Zeile mit demselben Primärschlüssel vorhanden ist.
Wie @Insert- und @Update-Funktionen akzeptieren auch @Upsert-Funktionen Datenentitätsinstanzen als Parameter. Der folgende Code zeigt ein Beispiel für eine @Upsert-Funktion, die versucht, ein oder mehrere User-Objekte in der Datenbank zu upserten:
@Dao interface UserDao { @Upsert suspend fun upsertUsers(vararg users: User) }
Wenn die @Upsert-Funktion einen einzelnen Parameter empfängt, kann sie einen Long-Wert zurückgeben. Wenn dadurch eine neue Zeile eingefügt wird, wird die rowId der neu eingefügten Zeile zurückgegeben. Wenn dadurch eine vorhandene Zeile aktualisiert wird, wird -1 zurückgegeben. Wenn der Parameter ein Array oder eine Sammlung ist, sollte stattdessen ein Array oder eine Sammlung von Long-Werten zurückgegeben werden.
Abfragefunktionen
Mit der @Query Annotation können Sie
SQL-Anweisungen schreiben und sie als DAO-Funktionen verfügbar machen. Verwenden Sie diese Abfragefunktionen, um Daten aus der Datenbank Ihrer App abzufragen oder wenn Sie komplexere Einfügungen, Aktualisierungen und Löschungen ausführen müssen.
Room validiert SQL-Abfragen zur Kompilierzeit. Wenn es ein Problem mit Ihrer Abfrage gibt, tritt also ein Kompilierungsfehler anstelle eines Laufzeitfehlers auf.
Einfache Abfragen
Der folgende Code definiert eine Funktion, die mit einer SELECT-Abfrage alle User-Objekte in der Datenbank zurückgibt:
@Query("SELECT * FROM user") suspend fun loadAllUsers(): List<User>
In den folgenden Abschnitten wird gezeigt, wie Sie dieses Beispiel für typische Anwendungsfälle ändern.
Teilmenge der Spalten einer Tabelle zurückgeben
Meistens müssen Sie nur eine Teilmenge der Spalten aus der Tabelle zurückgeben, die Sie abfragen. Auf der Benutzeroberfläche werden beispielsweise nur der Vor- und Nachname eines Nutzers anstelle aller Details zu diesem Nutzer angezeigt. Um Ressourcen zu sparen und die Ausführung der Abfrage zu optimieren, fragen Sie nur die Properties ab, die Sie benötigen.
Mit Room können Sie ein Datenobjekt aus jeder Ihrer Abfragen zurückgeben, solange Sie die Menge der Ergebnisspalten dem zurückgegebenen Objekt zuordnen können. Sie können beispielsweise das folgende Objekt definieren, um den Vor- und Nachnamen eines Nutzers zu speichern:
data class NameTuple( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String )
Anschließend können Sie dieses Datenobjekt aus Ihrer Abfragefunktion zurückgeben:
@Query("SELECT first_name, last_name FROM user") suspend fun loadFullName(): List<NameTuple>
Da die Abfrage Werte für die Spalten first_name und last_name zurückgibt, ordnet Room diese Werte den Properties in der Klasse NameTuple zu. Wenn die Abfrage eine Spalte zurückgibt, die keiner Property im zurückgegebenen Objekt zugeordnet ist, zeigt Room eine Warnung an.
Im vorherigen Beispiel wird eine benutzerdefinierte Datenklasse verwendet, um eine Teilmenge der Spalten abzurufen. Room unterstützt aber auch die Rückgabe von kotlin.Pair und kotlin.Triple, wenn eine Abfrage genau zwei oder drei Spalten zurückgibt. Bei Verwendung dieser Typen werden die Spalten in der Reihenfolge zugeordnet, in der sie in der Abfrageanweisung definiert sind. Die Reihenfolge der Spalten in der SELECT-Anweisung muss also mit der Reihenfolge der Typen in Pair oder Triple übereinstimmen.
Einfache Parameter an eine Abfrage übergeben
Meistens müssen Ihre DAO-Funktionen Parameter akzeptieren, damit sie Filtervorgänge ausführen können. Room unterstützt die Verwendung von Funktionsparametern als Bind-Parameter in Ihren Abfragen.
Der folgende Code definiert beispielsweise eine Funktion, die alle Nutzer über einem bestimmten Alter zurückgibt:
@Query("SELECT * FROM user WHERE age > :minAge") suspend fun loadAllUsersOlderThan(minAge: Int): Array<User>
Sie können auch mehrere Parameter übergeben oder mehrmals auf denselben Parameter in einer Abfrage verweisen, wie im folgenden Code gezeigt:
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") suspend fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User> @Query( """ SELECT * FROM user WHERE first_name LIKE :search OR last_name LIKE :search """ ) suspend fun findUserWithName(search: String): List<User>
Sammlung von Parametern an eine Abfrage übergeben
Für einige Ihrer DAO-Funktionen müssen Sie möglicherweise eine variable Anzahl von Parametern übergeben, die erst zur Laufzeit bekannt ist. Wenn ein Parameter eine Sammlung darstellt, wird er zur Laufzeit automatisch basierend auf der Anzahl der Werte erweitert.
Der folgende Code definiert beispielsweise eine Funktion, die Informationen zu allen Nutzern aus einer Teilmenge von Regionen zurückgibt:
@Query("SELECT * FROM user WHERE region IN (:regions)") suspend fun loadUsersFromRegions(regions: List<String>): List<User>
Mehrere Tabellen abfragen
Für einige Ihrer Abfragen ist möglicherweise der Zugriff auf mehrere Tabellen erforderlich, um das Ergebnis zu berechnen. Sie können in Ihren SQL-Abfragen JOIN-Klauseln verwenden, um auf mehr als eine Tabelle zu verweisen.
Der folgende Code definiert eine Funktion, die drei Tabellen verknüpft, um die Bücher zurückzugeben, die derzeit an einen bestimmten Nutzer ausgeliehen sind:
@Query( """ SELECT * FROM book INNER JOIN loan ON loan.book_id = book.id INNER JOIN user ON user.id = loan.user_id WHERE user.name LIKE :userName """ ) suspend fun findBooksBorrowedByName(userName: String): List<Book>
Sie können auch Datenobjekte definieren, um eine Teilmenge der Spalten aus mehreren verknüpften Tabellen zurückzugeben. Weitere Informationen finden Sie unter Teilmenge der Spalten einer Tabelle zurückgeben. Der folgende Code definiert ein DAO mit einer Funktion, die die Namen der Nutzer und die Namen der Bücher zurückgibt, die sie ausgeliehen haben:
interface UserBookDao { @Query( """ SELECT user.name AS userName, book.name AS bookName FROM user, book WHERE user.id = book.user_id """ ) fun loadUserAndBookNames(): Flow<List<UserBook>> } data class UserBook(val userName: String, val bookName: String)
Multimap zurückgeben
Für Verknüpfungsvorgänge können Sie auch Spalten aus mehreren Tabellen abfragen, ohne eine zusätzliche Datenklasse zu definieren, indem Sie Abfragefunktionen schreiben, die eine Multimapzurückgeben.
Sehen Sie sich das Beispiel unter Mehrere Tabellen abfragen an. Anstatt eine Liste von Instanzen einer benutzerdefinierten Datenklasse zurückzugeben, die Paare von User- und Book-Instanzen enthält, können Sie eine Zuordnung von User und Book direkt aus Ihrer Abfragefunktion zurückgeben:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNames(): Map<User, List<Book>>
Wenn Ihre Abfragefunktion eine Multimap zurückgibt, können Sie Abfragen schreiben, die GROUP BY-Klauseln verwenden. So können Sie die Funktionen von SQL für erweiterte Berechnungen und Filtervorgänge nutzen. Sie können beispielsweise die Funktion loadUserAndBookNames so ändern, dass nur Nutzer mit drei oder mehr ausgeliehenen Büchern zurückgegeben werden:
@Query( """ SELECT * FROM user JOIN book ON user.id = book.user_id GROUP BY user.name HAVING COUNT(book.id) >= 3 """ ) suspend fun loadUserAndBookNamesGrouped(): Map<User, List<Book>>
Wenn Sie keine vollständigen Objekte zuordnen müssen, können Sie auch Zuordnungen zwischen
bestimmten Spalten in Ihrer Abfrage zurückgeben. Verwenden Sie dazu die @MapColumn Annotation für die
generischen Parameter des Rückgabetyps.
@Query( """ SELECT user.name AS username, book.name AS bookname FROM user JOIN book ON user.id = book.user_id """ ) suspend fun loadUserAndBookNamesColumns(): Map< @MapColumn(columnName = "username") String, List<@MapColumn(columnName = "bookname") String> >
Spezielle Rückgabetypen
Room bietet einige spezielle Rückgabetypen für die Integration mit anderen API-Bibliotheken.
Paginierte Abfragen mit der Paging-Bibliothek
Room unterstützt paginierte Abfragen durch die Integration mit der Paging-Bibliothek. Wenn Sie Rückgabetypen von Paging 3 verwenden möchten, müssen Sie die Konverter für Rückgabetypen von Paging in Ihrer Datenbank oder Ihrem DAO registrieren:
- Fügen Sie das Artefakt
androidx.room3:room3-pagingin Ihre Build-Konfiguration ein. - Annotieren Sie Ihre
@Databaseoder@DaoDeklaration mit@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class).
Nach der Registrierung können Ihre DAOs PagingSource Objekte für die Verwendung mit
Paging 3 zurückgeben:
@Dao @DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class) interface UserDao { @Query("SELECT * FROM users WHERE label LIKE :query") fun pagingSource(query: String): PagingSource<Int, User> }
Weitere Informationen zum Auswählen von Typparametern für eine PagingSource finden Sie unter
Schlüssel- und Werttypen auswählen.
Direkter Zugriff auf die Datenbankverbindung
Wenn die Logik Ihrer App einen direkten Zugriff auf die Datenbankverbindung auf niedriger Ebene erfordert, können Sie stattdessen die Verbindungs-APIs von Room verwenden. Sie können eine
Verbindung mit
useReaderConnection
für schreibgeschützte Vorgänge oder
useWriterConnection
für Schreibvorgänge für Ihre RoomDatabase Instanz herstellen und mit
usePrepared
Anweisungen ausführen:
val result: List<Pair<Long, String>> = roomDatabase.useReaderConnection { connection -> connection.usePrepared( "SELECT * FROM user WHERE age > :minAge LIMIT 5" ) { stmt -> // Bind arguments if needed stmt.bindLong(1, minAge.toLong()) buildList { // Step through the results while (stmt.step()) { add(stmt.getLong(0) to stmt.getText(1)) } } } }
Wenn Sie Datenbanktransaktionen auf niedriger Ebene direkt für die
Verbindung ausführen müssen, können Sie die immediateTransaction,
deferredTransaction oder exclusiveTransaction Hilfsfunktionen für
eine Transactor-Instanz in einem useWriterConnection Block verwenden:
roomDatabase.useWriterConnection { transactor -> transactor.immediateTransaction { // Perform transactional database operations using transactor } }
Wenn Sie nur DAO-Vorgänge auf hoher Ebene in einer
Transaktion ausführen müssen, verwenden Sie stattdessen die withReadTransaction oder withWriteTransaction
Hilfserweiterungsfunktionen für Ihre RoomDatabase Instanz:
// Perform transactional read operations (DEFERRED transaction) val userCount = roomDatabase.withReadTransaction { userDao.countUsers() } // Perform transactional write operations (IMMEDIATE transaction) roomDatabase.withWriteTransaction { userDao.insert(newUser) userDao.update(existingUser) }