Доступ к данным с помощью Room DAO

Когда вы используете библиотеку постоянства комнаты для хранения данных вашего приложения, вы взаимодействуете с сохраненными данными, определяя объекты доступа к данным или DAO. Каждый DAO включает методы, которые предлагают абстрактный доступ к базе данных вашего приложения. Во время компиляции Room автоматически генерирует реализации определенных вами DAO.

Используя DAO для доступа к базе данных вашего приложения вместо построителей запросов или прямых запросов, вы можете сохранить разделение задач — важнейший архитектурный принцип. DAO также упрощают имитацию доступа к базе данных при тестировании приложения .

Анатомия ДАО

Вы можете определить каждый DAO либо как интерфейс, либо как абстрактный класс. В базовых случаях использования вы обычно используете интерфейс. В любом случае вы всегда должны аннотировать свои DAO с помощью @Dao . У DAO нет свойств, но они определяют один или несколько методов взаимодействия с данными в базе данных вашего приложения.

Следующий код является примером простого DAO, который определяет методы для вставки, удаления и выбора объектов User в базе данных Room:

Котлин

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

    @Delete
    fun delete(user: User)

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

Ява

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

    @Delete
    void delete(User user);

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

Существует два типа методов DAO, которые определяют взаимодействие с базой данных:

  • Удобные методы, позволяющие вставлять, обновлять и удалять строки в базе данных без написания кода SQL.
  • Методы запроса, позволяющие написать собственный SQL-запрос для взаимодействия с базой данных.

В следующих разделах показано, как использовать оба типа методов DAO для определения взаимодействий с базой данных, необходимых вашему приложению.

Удобные методы

Room предоставляет удобные аннотации для определения методов, которые выполняют простые вставки, обновления и удаления, не требуя написания инструкции SQL.

Если вам нужно определить более сложные вставки, обновления или удаления или если вам нужно запросить данные в базе данных, используйте вместо этого метод запроса .

Вставлять

Аннотация @Insert позволяет определять методы, которые вставляют свои параметры в соответствующую таблицу базы данных. В следующем коде показаны примеры допустимых методов @Insert , которые вставляют один или несколько объектов User в базу данных:

Котлин

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

Ява

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

Каждый параметр метода @Insert должен быть либо экземпляром класса объекта данных Room , аннотированным @Entity , либо коллекцией экземпляров класса объекта данных, каждый из которых указывает на базу данных. При вызове метода @Insert Room вставляет каждый переданный экземпляр сущности в соответствующую таблицу базы данных.

Если метод @Insert получает один параметр, он может вернуть long значение, которое является новым rowId для вставленного элемента. Если параметр является массивом или коллекцией, то вместо этого верните массив или коллекцию long значений, где каждое значение является rowId для одного из вставленных элементов. Дополнительные сведения о возврате значений rowId см. в справочной документации по аннотации @Insert и в документации SQLite для таблиц rowid .

Обновлять

Аннотация @Update позволяет определить методы, которые обновляют определенные строки в таблице базы данных. Подобно методам @Insert , методы @Update принимают экземпляры объектов данных в качестве параметров. В следующем коде показан пример метода @Update , который пытается обновить один или несколько объектов User в базе данных:

Котлин

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

Ява

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

Room использует первичный ключ для сопоставления переданных экземпляров сущностей со строками в базе данных. Если нет строки с тем же первичным ключом, Room не вносит изменений.

Метод @Update может при необходимости возвращать значение int указывающее количество успешно обновленных строк.

Удалить

Аннотация @Delete позволяет определить методы, удаляющие определенные строки из таблицы базы данных. Подобно методам @Insert , методы @Delete принимают экземпляры объектов данных в качестве параметров. В следующем коде показан пример метода @Delete , который пытается удалить один или несколько объектов User из базы данных:

Котлин

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

Ява

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

Room использует первичный ключ для сопоставления переданных экземпляров сущностей со строками в базе данных. Если нет строки с тем же первичным ключом, Room не вносит изменений.

Метод @Delete может при необходимости возвращать значение int указывающее количество успешно удаленных строк.

Методы запроса

Аннотация @Query позволяет писать операторы SQL и предоставлять их как методы DAO. Используйте эти методы запроса для запроса данных из базы данных вашего приложения или когда вам нужно выполнить более сложные вставки, обновления и удаления.

Room проверяет SQL-запросы во время компиляции. Это означает, что если с вашим запросом возникает проблема, вместо сбоя во время выполнения возникает ошибка компиляции.

Простые запросы

Следующий код определяет метод, который использует простой запрос SELECT для возврата всех объектов User в базе данных:

Котлин

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

Ява

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

В следующих разделах показано, как изменить этот пример для типичных случаев использования.

Вернуть подмножество столбцов таблицы

В большинстве случаев вам нужно вернуть только подмножество столбцов из запрашиваемой таблицы. Например, ваш пользовательский интерфейс может отображать только имя и фамилию пользователя вместо всех подробностей об этом пользователе. Чтобы сэкономить ресурсы и оптимизировать выполнение запроса, запрашивайте только те поля, которые вам нужны.

Room позволяет вам возвращать простой объект из любого вашего запроса, если вы можете сопоставить набор столбцов результатов с возвращаемым объектом. Например, вы можете определить следующий объект для хранения имени и фамилии пользователя:

Котлин

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

Ява

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

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

Затем вы можете вернуть этот простой объект из метода запроса:

Котлин

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

Ява

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

Room понимает, что запрос возвращает значения для столбцов first_name и last_name и что эти значения можно сопоставить с полями в классе NameTuple . Если запрос возвращает столбец, который не сопоставляется с полем возвращаемого объекта, Room отображает предупреждение.

Передача простых параметров в запрос

В большинстве случаев вашим методам DAO необходимо принимать параметры, чтобы они могли выполнять операции фильтрации. Room поддерживает использование параметров метода в качестве параметров привязки в ваших запросах.

Например, следующий код определяет метод, который возвращает всех пользователей старше определенного возраста:

Котлин

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

Ява

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

Вы также можете передать несколько параметров или несколько раз ссылаться на один и тот же параметр в запросе, как показано в следующем коде:

Котлин

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

Ява

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

Передать набор параметров в запрос

Некоторые из ваших методов DAO могут потребовать от вас передачи переменного количества параметров, которое неизвестно до времени выполнения. Room понимает, когда параметр представляет коллекцию, и автоматически расширяет ее во время выполнения в зависимости от количества предоставленных параметров.

Например, следующий код определяет метод, который возвращает информацию обо всех пользователях из подмножества регионов:

Котлин

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

Ява

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

Запросить несколько таблиц

Для некоторых ваших запросов может потребоваться доступ к нескольким таблицам для вычисления результата. Вы можете использовать предложения JOIN в своих запросах SQL для ссылки на более чем одну таблицу.

Следующий код определяет метод, который объединяет три таблицы для возврата книг, которые в данный момент предоставлены конкретному пользователю:

Котлин

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

Ява

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

Вы также можете определить простые объекты для возврата подмножества столбцов из нескольких объединенных таблиц, как описано в разделе «Возврат подмножества столбцов таблицы» . Следующий код определяет DAO с методом, который возвращает имена пользователей и названия книг, которые они позаимствовали:

Котлин

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

Ява

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

Вернуть мультикарту

В версии 2.4 и выше вы также можете запрашивать столбцы из нескольких таблиц без определения дополнительного класса данных, написав методы запроса, которые возвращают multimap .

Рассмотрим пример из раздела «Запрос к нескольким таблицам» . Вместо возврата списка экземпляров пользовательского класса данных, содержащего пары экземпляров User и Book , вы можете вернуть сопоставление User и Book непосредственно из метода запроса:

Котлин

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

Ява

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

Когда ваш метод запроса возвращает мультикарту, вы можете писать запросы, использующие предложения GROUP BY , что позволяет вам воспользоваться возможностями SQL для расширенных вычислений и фильтрации. Например, вы можете изменить метод loadUserAndBookNames() , чтобы он возвращал только пользователей с тремя или более извлеченными книгами:

Котлин

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

Ява

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

Если вам не нужно сопоставлять целые объекты, вы также можете вернуть сопоставления между конкретными столбцами в вашем запросе, установив атрибуты keyColumn и valueColumn в аннотации @MapInfo для вашего метода запроса:

Котлин

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

Ява

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

Специальные типы возврата

Room предоставляет некоторые специальные типы возврата для интеграции с другими библиотеками API.

Запросы с разбиением на страницы с помощью библиотеки подкачки

Room поддерживает запросы с разбиением на страницы благодаря интеграции с библиотекой подкачки . В Room 2.3.0-alpha01 и выше DAO могут возвращать объекты PagingSource для использования с Paging 3 .

Котлин

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

Ява

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

Дополнительные сведения о выборе параметров типа для PagingSource см. в разделе Выбор типов ключа и значения .

Прямой доступ к курсору

Если логика вашего приложения требует прямого доступа к возвращаемым строкам, вы можете написать методы DAO для возврата объекта Cursor , как показано в следующем примере:

Котлин

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

Ява

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

Дополнительные ресурсы

Чтобы узнать больше о доступе к данным с помощью Room DAO, см. следующие дополнительные ресурсы:

Образцы

Кодлабы