Uzyskiwanie dostępu do danych za pomocą funkcji DAO dla sal

Jeśli do przechowywania danych aplikacji używasz biblioteki trwałości pokoju, możesz z nich korzystać, definiując obiekty dostępu do danych (DAO). Każdy DAO obejmuje metody zapewniające abstrakcyjny dostęp do bazy danych aplikacji. Podczas kompilacji funkcja Room automatycznie generuje implementacje zdefiniowanych przez Ciebie komponentów DAO.

Jeśli będziesz używać DAO do uzyskiwania dostępu do bazy danych aplikacji zamiast konstruktorów zapytań lub bezpośrednich zapytań, możesz zachować oddzielenie potencjalnych problemów, która jest kluczową zasadą architektury. Ułatwiają one też symulowanie dostępu do bazy danych podczas testowania aplikacji.

Anatomia DAO

Każdej aplikacji DAO można zdefiniować jako interfejs lub klasę abstrakcyjną. W podstawowych przypadkach używa się zwykle interfejsu. W obu przypadkach w przypadku aktywnych użytkowników zawsze musisz dodawać adnotacje w postaci @Dao. DAO nie mają właściwości, ale definiują co najmniej jedną metodę interakcji z danymi znajdującymi się w bazie danych aplikacji.

Poniższy kod to przykład prostego DAO, który definiuje metody wstawiania, usuwania i wybierania obiektów User w bazie danych sal:

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

Istnieją 2 typy metod DAO definiujących interakcje z bazą danych:

  • Wygodne metody, które pozwalają wstawiać, aktualizować i usuwać wiersze w bazie danych bez konieczności pisania kodu SQL.
  • Metody zapytań, które pozwalają napisać własne zapytanie SQL na potrzeby interakcji z bazą danych.

W sekcjach poniżej pokazujemy, jak używać obu typów metod DAO do definiowania interakcji z bazą danych potrzebnych aplikacji.

Wygodne metody

Aplikacja Room zawiera przydatne adnotacje służące do definiowania metod, które wykonują proste wstawianie, aktualizowanie i usuwanie bez konieczności pisania instrukcji SQL.

Jeśli musisz zdefiniować bardziej złożone wstawienia, aktualizacje lub usunięcia albo wysyłać zapytania o dane w bazie danych, użyj metody zapytania.

Wstaw

Adnotacja @Insert umożliwia definiowanie metod, które wstawiają swoje parametry do odpowiedniej tabeli w bazie danych. Poniższy kod zawiera przykłady prawidłowych metod @Insert, które wstawiają do bazy danych co najmniej 1 obiekt User:

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

Każdy parametr metody @Insert musi być instancją klasy encji danych o sali z adnotacją @Entity lub zbiorem instancji klas encji danych, z których każdy wskazuje bazę danych. Po wywołaniu metody @Insert Room wstawia każdą przekazaną instancję encji do odpowiedniej tabeli bazy danych.

Jeśli metoda @Insert otrzyma jeden parametr, może zwrócić wartość long, czyli nowy parametr rowId dla wstawionego elementu. Jeśli parametr jest tablicą lub kolekcją, zamiast niej zwracaj tablicę lub zbiór wartości long, przy czym każda wartość to rowId dla jednego z wstawionych elementów. Więcej informacji o zwracaniu wartości rowId znajdziesz w dokumentacji adnotacji @Insert i dokumentacji SQLite dotyczącej tabel rowid.

Aktualizuj

Adnotacja @Update pozwala zdefiniować metody, które aktualizują określone wiersze w tabeli bazy danych. Podobnie jak metody @Insert, metody @Update akceptują wystąpienia encji danych jako parametry. Poniższy kod przedstawia przykład metody @Update, która próbuje zaktualizować co najmniej 1 obiekt User w bazie danych:

Kotlin

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

Java

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

Za pomocą klucza podstawowego sala dopasowuje przekazane instancje encji do wierszy w bazie danych. Jeśli nie ma wiersza z tym samym kluczem podstawowym, funkcja Room nie wprowadza żadnych zmian.

Metoda @Update może opcjonalnie zwracać wartość int wskazującą liczbę wierszy, które zostały zaktualizowane.

Usuń

Adnotacja @Delete umożliwia definiowanie metod, które usuwają określone wiersze z tabeli bazy danych. Podobnie jak metody @Insert, metody @Delete akceptują wystąpienia encji danych jako parametry. Poniższy kod przedstawia przykład metody @Delete, która próbuje usunąć z bazy danych co najmniej 1 obiekt User:

Kotlin

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

Java

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

Za pomocą klucza podstawowego sala dopasowuje przekazane instancje encji do wierszy w bazie danych. Jeśli nie ma wiersza z tym samym kluczem podstawowym, funkcja Room nie wprowadza żadnych zmian.

Metoda @Delete może opcjonalnie zwracać wartość int wskazującą liczbę wierszy, które zostały usunięte.

Metody zapytań

Adnotacja @Query umożliwia pisanie instrukcji SQL i udostępnianie ich jako metod DAO. Korzystaj z tych metod zapytań, aby wykonywać zapytania o dane z bazy danych aplikacji lub gdy musisz wykonywać bardziej złożone wstawianie, aktualizowanie i usuwanie.

Sala weryfikuje zapytania SQL podczas kompilacji. Oznacza to, że jeśli wystąpi problem z zapytaniem, zamiast błędu środowiska wykonawczego wystąpi błąd kompilacji.

Proste zapytania

Poniższy kod określa metodę, która wykorzystuje proste zapytanie SELECT do zwrócenia wszystkich obiektów User w bazie danych:

Kotlin

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

Java

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

Sekcje poniżej pokazują, jak zmodyfikować ten przykład w typowych przypadkach użycia.

Zwraca podzbiór kolumn tabeli

Zwykle musisz zwrócić tylko podzbiór kolumn z tabeli, której dotyczy zapytanie. Na przykład Twój interfejs może wyświetlać tylko imię i nazwisko użytkownika, a nie wszystkie informacje o nim. Aby zapisać zasoby i uprościć wykonywanie zapytania, wysyłaj zapytania tylko do tych pól, które są Ci potrzebne.

Pokoje pozwalają zwrócić prosty obiekt z dowolnego zapytania, pod warunkiem że możesz zmapować zestaw kolumn wyników na zwracany obiekt. Możesz na przykład zdefiniować taki obiekt, który będzie przechowywał imię i nazwisko użytkownika:

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

Następnie możesz zwrócić ten prosty obiekt za pomocą metody zapytania:

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();

Sala rozumie, że zapytanie zwraca wartości kolumn first_name i last_name oraz że wartości te można zmapować na pola w klasie NameTuple. Jeśli zapytanie zwróci kolumnę, która nie jest mapowana na pole w zwróconym obiekcie, funkcja Room wyświetla ostrzeżenie.

Przekazywanie prostych parametrów do zapytania

W większości przypadków metody DAO muszą akceptować parametry, aby mogły wykonywać operacje filtrowania. Sala obsługuje w zapytaniach parametry metody jako parametry powiązania.

Na przykład ten kod definiuje metodę, która zwraca wszystkich użytkowników, którzy ukończyli określony wiek:

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

W zapytaniu możesz też przekazywać wiele parametrów lub odwoływać się do tego samego parametru wielokrotnie, jak pokazano w tym kodzie:

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

Przekazywanie zbioru parametrów do zapytania

Niektóre z Twoich metod DAO mogą wymagać przekazywania zmiennej liczby parametrów, których nie znamy do momentu uruchomienia. Funkcja Room rozumie, kiedy parametr reprezentuje kolekcję, i automatycznie rozwija go w czasie działania na podstawie liczby podanych parametrów.

Na przykład ten kod definiuje metodę, która zwraca informacje o wszystkich użytkownikach z podzbioru regionów:

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

Tworzenie zapytania do wielu tabel

Niektóre zapytania mogą wymagać dostępu do wielu tabel, aby obliczyć wynik. Aby odwoływać się do więcej niż 1 tabeli, w zapytaniach SQL możesz używać klauzul JOIN.

Poniższy kod określa metodę łączącą 3 tabele w celu zwrócenia książek, które są obecnie wypożyczone określonemu użytkownikowi:

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

Możesz też zdefiniować proste obiekty, które będą zwracać podzbiór kolumn z wielu połączonych tabel, jak omówiliśmy w sekcji Zwracanie podzbioru kolumn tabeli. Poniższy kod definiuje DAO z metodą, która zwraca nazwy użytkowników i nazwy wypożyczonych przez nich książek:

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

Zwracanie multimapy

W Room 2.4 lub nowszym możesz tworzyć zapytania dotyczące kolumn z wielu tabel bez definiowania dodatkowej klasy danych. W tym celu możesz tworzyć metody zapytań, które zwracają multimapę.

Zapoznaj się z przykładem w sekcji Wysyłanie zapytań do wielu tabel. Zamiast zwracać listę instancji niestandardowej klasy danych, która zawiera pary instancji User i Book, możesz zwrócić mapowanie User i Book bezpośrednio za pomocą metody zapytania:

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();

Gdy Twoja metoda zapytania zwraca wiele map, możesz tworzyć zapytania korzystające z klauzul GROUP BY, co pozwala korzystać z możliwości SQL do zaawansowanych obliczeń i filtrowania. Możesz np. zmodyfikować metodę loadUserAndBookNames() tak, aby zwracała tylko tych użytkowników, którzy wymeldowali się co najmniej 3 książki:

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();

Jeśli nie musisz mapować całych obiektów, możesz też zwrócić mapowania między konkretnymi kolumnami w zapytaniu, ustawiając atrybuty keyColumn i valueColumn w adnotacji @MapInfo metody zapytania:

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();

Specjalne typy zwrotów

Sala udostępnia specjalne typy zwrotów na potrzeby integracji z innymi bibliotekami interfejsów API.

Zapytania z podziałem na strony za pomocą biblioteki stronicowania

Sala obsługuje zapytania podzielone na strony dzięki integracji z biblioteką stronicowania. W salach 2.3.0-alfa01 i nowszych modele DAO mogą zwracać obiekty PagingSource do użycia z Paging 3.

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

Więcej informacji o wybieraniu parametrów typu dla właściwości PagingSource znajdziesz w artykule o wybieraniu typów klucza i wartości.

Bezpośredni dostęp do kursora

Jeśli logika aplikacji wymaga bezpośredniego dostępu do zwracanych wierszy, możesz napisać metody DAO, które będą zwracać obiekt Cursor, jak w tym przykładzie:

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

Dodatkowe materiały

Więcej informacji o dostępie do danych za pomocą funkcji DAO dla sal znajdziesz w tych materiałach dodatkowych:

Próbki

Ćwiczenia z programowania