Mit Room-DAOs auf Daten zugreifen

Wenn Sie die Room-Persistence-Bibliothek zum Speichern der Daten Ihrer Anwendung verwenden, interagieren Sie mit den gespeicherten Daten, indem Sie Data Access Objects (DAOs) definieren. Jede DAO enthält Methoden, die abstrakten Zugriff auf die Datenbank Ihrer Anwendung ermöglichen. Zur Kompilierungszeit generiert Room automatisch Implementierungen der von Ihnen definierten DAOs.

Wenn Sie DAOs für den Zugriff auf die Datenbank Ihrer Anwendung anstelle von Abfrage-Buildern oder direkten Abfragen verwenden, können Sie die Trennung von Angelegenheiten wahren, ein wichtiges Architekturprinzip. DAOs erleichtern Ihnen auch das Simulieren des Datenbankzugriffs, wenn Sie Ihre Anwendung testen.

Anatomie eines DAO

Sie können jedes DAO entweder als Schnittstelle oder als abstrakte Klasse definieren. Für grundlegende Anwendungsfälle verwenden Sie normalerweise eine Schnittstelle. In beiden Fällen müssen Sie Ihre DAOs immer mit @Dao annotieren. DAOs haben keine Attribute, definieren aber eine oder mehrere Methoden für die Interaktion mit den Daten in der Datenbank Ihrer Anwendung.

Der folgende Code ist ein Beispiel für einen einfachen DAO, der Methoden zum Einfügen, Löschen und Auswählen von User-Objekten in einer Raumdatenbank definiert:

Kotlin

@Dao
interface UserDao {
    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)

    @Query("SELECT * FROM user")
    fun getAll(): List<User>
}

Java

@Dao
public interface UserDao {
    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);

    @Query("SELECT * FROM user")
    List<User> getAll();
}

Es gibt zwei Arten von DAO-Methoden, mit denen Datenbankinteraktionen definiert werden:

  • Praktische Methoden, mit denen Sie Zeilen in Ihrer Datenbank einfügen, aktualisieren und löschen können, ohne SQL-Code schreiben zu müssen.
  • Abfragemethoden, mit denen Sie eine eigene SQL-Abfrage schreiben können, um mit der Datenbank zu interagieren

In den folgenden Abschnitten wird gezeigt, wie Sie beide Arten von DAO-Methoden verwenden, um die Datenbankinteraktionen zu definieren, die Ihre Anwendung benötigt.

Praktische Methoden

Room bietet praktische Anmerkungen zum Definieren von Methoden, die einfache Einfügungen, Aktualisierungen und Löschungen ausführen, ohne dass Sie eine SQL-Anweisung schreiben müssen.

Wenn Sie komplexere Einfügungen, Aktualisierungen oder Löschungen definieren oder die Daten in der Datenbank abfragen müssen, verwenden Sie stattdessen eine Abfragemethode.

Einfügen

Mit der Annotation @Insert können Sie Methoden definieren, die ihre Parameter in die entsprechende Tabelle in der Datenbank einfügen. Der folgende Code zeigt Beispiele für gültige @Insert-Methoden, mit denen ein oder mehrere User-Objekte in die Datenbank eingefügt werden:

Kotlin

@Dao
interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUsers(vararg users: User)

    @Insert
    fun insertBothUsers(user1: User, user2: User)

    @Insert
    fun insertUsersAndFriends(user: User, friends: List<User>)
}

Java

@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertUsers(User... users);

    @Insert
    public void insertBothUsers(User user1, User user2);

    @Insert
    public void insertUsersAndFriends(User user, List<User> friends);
}

Jeder Parameter für eine @Insert-Methode muss entweder eine Instanz einer mit @Entity annotierten Raumdatenentitätsklasse oder eine Sammlung von Instanzen einer Datenentitätsklasse sein, die jeweils auf eine Datenbank verweisen. Beim Aufruf einer @Insert-Methode fügt Room jede übergebene Entitätsinstanz in die entsprechende Datenbanktabelle ein.

Wenn die Methode @Insert einen einzelnen Parameter empfängt, kann sie einen long-Wert zurückgeben. Dies ist der neue rowId-Wert für das eingefügte Element. Wenn der Parameter ein Array oder eine Sammlung ist, wird stattdessen ein Array oder eine Sammlung von long-Werten zurückgegeben, wobei jeder Wert für eines der eingefügten Elemente der rowId ist. Weitere Informationen zur Rückgabe von rowId-Werten finden Sie in der Referenzdokumentation zur Annotation @Insert und in der SQLite-Dokumentation für rowid-Tabellen.

Aktualisieren

Mit der Annotation @Update können Sie Methoden definieren, mit denen bestimmte Zeilen in einer Datenbanktabelle aktualisiert werden. Wie die @Insert-Methoden akzeptieren auch @Update-Methoden Datenentitätsinstanzen als Parameter. Der folgende Code zeigt ein Beispiel für eine @Update-Methode, mit der versucht wird, ein oder mehrere User-Objekte in der Datenbank zu aktualisieren:

Kotlin

@Dao
interface UserDao {
    @Update
    fun updateUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Update
    public void updateUsers(User... users);
}

Room verwendet den Primärschlüssel, um übergebene Entitätsinstanzen den Zeilen in der Datenbank zuzuordnen. Wenn keine Zeile mit demselben Primärschlüssel vorhanden ist, nimmt Room keine Änderungen vor.

Eine @Update-Methode kann optional einen int-Wert zurückgeben, der die Anzahl der erfolgreich aktualisierten Zeilen angibt.

Löschen

Mit der Annotation @Delete können Sie Methoden definieren, mit denen bestimmte Zeilen aus einer Datenbanktabelle gelöscht werden. Wie die @Insert-Methoden akzeptieren auch @Delete-Methoden Datenentitätsinstanzen als Parameter. Der folgende Code zeigt ein Beispiel für eine @Delete-Methode, mit der versucht wird, ein oder mehrere User-Objekte aus der Datenbank zu löschen:

Kotlin

@Dao
interface UserDao {
    @Delete
    fun deleteUsers(vararg users: User)
}

Java

@Dao
public interface UserDao {
    @Delete
    public void deleteUsers(User... users);
}

Room verwendet den Primärschlüssel, um übergebene Entitätsinstanzen den Zeilen in der Datenbank zuzuordnen. Wenn keine Zeile mit demselben Primärschlüssel vorhanden ist, nimmt Room keine Änderungen vor.

Die Methode @Delete kann optional einen int-Wert zurückgeben, der die Anzahl der erfolgreich gelöschten Zeilen angibt.

Abfragemethoden

Mit der Annotation @Query können Sie SQL-Anweisungen schreiben und als DAO-Methoden verfügbar machen. Mit diesen Abfragemethoden können Sie Daten aus der Datenbank Ihrer Anwendung abfragen oder wenn Sie komplexere Einfügungen, Aktualisierungen und Löschungen ausführen müssen.

Room validiert SQL-Abfragen zum Zeitpunkt der Kompilierung. Wenn also mit Ihrer Abfrage ein Problem auftritt, tritt anstelle eines Laufzeitfehlers ein Kompilierungsfehler auf.

Einfache Abfragen

Der folgende Code definiert eine Methode, die mit einer einfachen SELECT-Abfrage alle User-Objekte in der Datenbank zurückgibt:

Kotlin

@Query("SELECT * FROM user")
fun loadAllUsers(): Array<User>

Java

@Query("SELECT * FROM user")
public User[] loadAllUsers();

In den folgenden Abschnitten wird gezeigt, wie Sie dieses Beispiel für typische Anwendungsfälle ändern können.

Teilmenge der Spalten einer Tabelle zurückgeben

Meistens müssen Sie nur eine Teilmenge der Spalten aus der Tabelle zurückgeben, die Sie abfragen. Ihre UI kann beispielsweise nur den Vor- und Nachnamen für einen Nutzer anstelle aller Details zu diesem Nutzer anzeigen. Fragen Sie nur die Felder ab, die Sie benötigen, um Ressourcen zu sparen und die Ausführung Ihrer Abfrage zu optimieren.

Mit Room können Sie ein einfaches Objekt aus einer Ihrer Abfragen zurückgeben, solange Sie die Ergebnisspalten dem zurückgegebenen Objekt zuordnen können. Sie können beispielsweise das folgende Objekt definieren, das den Vor- und Nachnamen eines Nutzers enthält:

Kotlin

data class NameTuple(
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

Java

public class NameTuple {
    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    @NonNull
    public String lastName;
}

Anschließend können Sie dieses einfache Objekt aus Ihrer Abfragemethode zurückgeben:

Kotlin

@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>

Java

@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();

Room erkennt, dass die Abfrage Werte für die Spalten first_name und last_name zurückgibt und dass diese Werte den Feldern in der Klasse NameTuple zugeordnet werden können. Wenn die Abfrage eine Spalte zurückgibt, die keinem Feld im zurückgegebenen Objekt zugeordnet ist, zeigt Room eine Warnung an.

Einfache Parameter an eine Abfrage übergeben

In den meisten Fällen müssen Ihre DAO-Methoden Parameter akzeptieren, damit sie Filtervorgänge ausführen können. Room unterstützt die Verwendung von Methodenparametern als Bindungsparameter in Ihren Abfragen.

Der folgende Code definiert beispielsweise eine Methode, die alle Nutzer ab einem bestimmten Alter zurückgibt:

Kotlin

@Query("SELECT * FROM user WHERE age > :minAge")
fun loadAllUsersOlderThan(minAge: Int): Array<User>

Java

@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);

Sie können auch mehrere Parameter übergeben oder in einer Abfrage mehrmals auf denselben Parameter verweisen, wie im folgenden Code gezeigt:

Kotlin

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
fun findUserWithName(search: String): List<User>

Java

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

@Query("SELECT * FROM user WHERE first_name LIKE :search " +
       "OR last_name LIKE :search")
public List<User> findUserWithName(String search);

Sammlung von Parametern an eine Abfrage übergeben

Bei einigen Ihrer DAO-Methoden müssen Sie möglicherweise eine variable Anzahl von Parametern übergeben, die bis zur Laufzeit nicht bekannt sind. Room erkennt, wann ein Parameter eine Sammlung darstellt, und erweitert sie zur Laufzeit automatisch auf Grundlage der Anzahl der angegebenen Parameter.

Beispielsweise definiert der folgende Code eine Methode, die Informationen zu allen Nutzern aus einer Teilmenge von Regionen zurückgibt:

Kotlin

@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>

Java

@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);

Mehrere Tabellen abfragen

Einige Ihrer Abfragen benötigen möglicherweise Zugriff auf mehrere Tabellen, um das Ergebnis zu berechnen. Sie können JOIN-Klauseln in Ihren SQL-Abfragen verwenden, um auf mehr als eine Tabelle zu verweisen.

Der folgende Code definiert eine Methode, mit der drei Tabellen miteinander verknüpft werden, um die Bücher zurückzugeben, die derzeit einem bestimmten Nutzer ausgeliehen werden:

Kotlin

@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"
)
fun findBooksBorrowedByNameSync(userName: String): List<Book>

Java

@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")
public List<Book> findBooksBorrowedByNameSync(String userName);

Sie können auch einfache Objekte definieren, um eine Teilmenge von Spalten aus mehreren verknüpften Tabellen zurückzugeben, wie im Abschnitt Eine Teilmenge der Spalten einer Tabelle zurückgeben beschrieben. Der folgende Code definiert einen DAO mit einer Methode, die die Namen von Nutzern und die Namen der von ihnen ausgeliehenen Bücher zurückgibt:

Kotlin

interface UserBookDao {
    @Query(
        "SELECT user.name AS userName, book.name AS bookName " +
        "FROM user, book " +
        "WHERE user.id = book.user_id"
    )
    fun loadUserAndBookNames(): LiveData<List<UserBook>>

    // You can also define this class in a separate file.
    data class UserBook(val userName: String?, val bookName: String?)
}

Java

@Dao
public interface UserBookDao {
   @Query("SELECT user.name AS userName, book.name AS bookName " +
          "FROM user, book " +
          "WHERE user.id = book.user_id")
   public LiveData<List<UserBook>> loadUserAndBookNames();

   // You can also define this class in a separate file, as long as you add the
   // "public" access modifier.
   static class UserBook {
       public String userName;
       public String bookName;
   }
}

Multimap zurückgeben

In Raum 2.4 und höher können Sie auch Spalten aus mehreren Tabellen abfragen, ohne eine zusätzliche Datenklasse zu definieren. Dazu schreiben Sie Abfragemethoden, die eine Multimap zurückgeben.

Betrachten Sie das Beispiel aus dem Abschnitt Mehrere Tabellen abfragen. 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 über Ihre Abfragemethode zurückgeben:

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<User, List<Book>> loadUserAndBookNames();

Wenn Ihre Abfragemethode eine Multimap zurückgibt, können Sie Abfragen schreiben, die GROUP BY-Klauseln verwenden. Dadurch können Sie die SQL-Funktionen für erweiterte Berechnungen und Filter nutzen. Du kannst beispielsweise die Methode loadUserAndBookNames() so ändern, dass nur Nutzer zurückgegeben werden, die drei oder mehr Bücher ausgecheckt haben:

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
public Map<User, List<Book>> loadUserAndBookNames();

Wenn Sie keine gesamten Objekte zuordnen müssen, können Sie auch Zuordnungen zwischen bestimmten Spalten in Ihrer Abfrage zurückgeben. Legen Sie dazu die Attribute keyColumn und valueColumn in einer @MapInfo-Annotation Ihrer Abfragemethode fest:

Kotlin

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<String, List<String>>

Java

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<String, List<String>> loadUserAndBookNames();

Spezielle Rückgabearten

Room bietet einige spezielle Rückgabetypen für die Integration in andere API-Bibliotheken.

Abfragen mit Paginierung mit der Paging-Bibliothek

Room unterstützt paginierte Abfragen über die Einbindung in die Paging-Bibliothek. In Room 2.3.0-alpha01 und höher können DAOs PagingSource-Objekte zur Verwendung mit Paging 3 zurückgeben.

Kotlin

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  fun pagingSource(query: String): PagingSource<Int, User>
}

Java

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  PagingSource<Integer, User> pagingSource(String query);
}

Weitere Informationen zur Auswahl von Typparametern für ein PagingSource finden Sie unter Schlüssel- und Werttypen auswählen.

Direkter Cursorzugriff

Wenn die Anwendungslogik direkten Zugriff auf die Rückgabezeilen erfordert, können Sie Ihre DAO-Methoden so schreiben, dass ein Cursor-Objekt zurückgegeben wird, wie im folgenden Beispiel gezeigt:

Kotlin

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    fun loadRawUsersOlderThan(minAge: Int): Cursor
}

Java

@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}

Weitere Informationen

Weitere Informationen zum Zugriff auf Daten mit Room-DAOs finden Sie in den folgenden zusätzlichen Ressourcen:

Produktproben

Codelabs