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: