Чтобы запросы не блокировали пользовательский интерфейс, Room не поддерживает доступ к базе данных в основном потоке. Это ограничение означает, что ваши запросы к DAO должны быть асинхронными. Библиотека Room включает интеграцию с несколькими фреймворками для обеспечения асинхронного выполнения запросов.
Запросы к DAO делятся на три категории:
- Одноразовые запросы на запись , которые вставляют, обновляют или удаляют данные в базе данных.
- Одноразовые запросы на чтение , которые считывают данные из вашей базы данных только один раз и возвращают результат, содержащий снимок базы данных на тот момент времени.
- Наблюдаемые запросы на чтение , которые считывают данные из вашей базы данных каждый раз, когда изменяются базовые таблицы базы данных, и выдают новые значения, отражающие эти изменения.
Языковые и фреймворковые параметры
Room обеспечивает интеграцию для взаимодействия с конкретными языковыми функциями и библиотеками. В следующей таблице показаны применимые типы возвращаемых значений в зависимости от типа запроса и используемой платформы:
| тип запроса | Особенности языка Kotlin (нативные) | RxJava | Гуава | Жизненный цикл реактивного ранца* |
|---|---|---|---|---|
| Однократная запись | Корутины ( suspend ) | Single<T> , Maybe<T> , Completable | ListenableFuture<T> | Н/Д |
| Чтение на один раз | Корутины ( suspend ) | Single<T> , Maybe<T> | ListenableFuture<T> | Н/Д |
| Наблюдаемое чтение | Flow<T> | Flowable<T> , Publisher<T> , Observable<T> | Н/Д | LiveData<T> |
В этом руководстве показаны три способа использования этих интеграций для реализации асинхронных запросов в ваших DAO.
Kotlin с Flow и сопрограммами
Kotlin предоставляет встроенные языковые возможности, позволяющие писать асинхронные запросы без использования сторонних фреймворков:
- Room напрямую поддерживает Kotlin Flow для написания наблюдаемых запросов.
- Для асинхронной обработки одноразовых запросов к DAO с помощью сопрограмм Kotlin в Room требуется ключевое слово
suspend.
Поддержка сопрограмм и потоков встроена непосредственно в основную среду выполнения Room, поэтому никаких дополнительных артефактов не требуется.
RxJava для Kotlin и Java
Room 3.0 поддерживает возвращаемые типы данных RxJava 3. Для использования возвращаемых типов данных RxJava необходимо зарегистрировать преобразователи возвращаемых типов RxJava в вашей базе данных или DAO:
- Включите артефакт
androidx.room3:room3-rxjava3в конфигурацию сборки. - Добавьте аннотацию
@Databaseили@Daoк вашему объявлению@DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).
Room поддерживает следующие типы возвращаемых значений RxJava 3:
- Одноразовые запросы :
Completable,Single<T>иMaybe<T> - Observable-запросы :
Publisher<T>,Flowable<T>иObservable<T>
LiveData и Guava
Room 3.0 поддерживает возвращаемые типы данных LiveData и Guava ListenableFuture с помощью конвертеров:
- LiveData : Включите артефакт
androidx.room3:room3-livedataи аннотируйте свою базу данных или DAO с помощью@DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class). - Guava : Включите артефакт
androidx.room3:room3-guavaи аннотируйте свою базу данных или DAO с помощью@DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).
Пишите асинхронные одноразовые запросы
Одноразовые запросы — это операции с базой данных, которые выполняются только один раз и фиксируют состояние данных на момент выполнения. Вот несколько примеров асинхронных одноразовых запросов:
@Dao interface UserDao { @Query("SELECT * FROM user WHERE id = :id") suspend fun loadUserById(id: Int): User @Query("SELECT * from user WHERE region IN (:regions)") suspend fun loadUsersByRegion(regions: List<String>): List<User> }
Пишите наблюдаемые запросы
Наблюдаемые запросы — это операции чтения, которые генерируют новые значения всякий раз, когда изменяются таблицы, на которые они ссылаются. Например, вы можете использовать это поведение для обновления отображаемого списка элементов по мере изменения базы данных. Вот несколько примеров наблюдаемых запросов:
@Dao interface ObservableUserDao { @Query("SELECT * FROM user WHERE id = :id") fun loadUserById(id: Int): Flow<User> @Query("SELECT * from user WHERE region IN (:regions)") fun loadUsersByRegion(regions: List<String>): Flow<List<User>> }
Отслеживание аннулирования базы данных вручную
Если вам необходимо вручную создавать наблюдаемые операции с базой данных, вы можете использовать API createFlow из InvalidationTracker . Этот API позволяет создать Flow , который отслеживает изменения в определенных таблицах и отправляет уведомление всякий раз, когда эти таблицы изменяются.
fun getArtistTours(db: RoomDatabase, from: Date, to: Date): Flow<Map<Artist, TourState>> { return db.invalidationTracker.createFlow("Artist").map { _ -> val artists = artistsDao.getAllArtists() val tours = tourService.fetchStates(artists.map { it.id }) associateTours(artists, tours, from, to) } }
По умолчанию возвращаемый Flow выдает начальное значение, содержащее все зарегистрированные таблицы, для запуска потока. Вы можете отключить это поведение, установив параметр emitInitialState в false .
Пользовательские преобразователи типов возврата DAO
Для типов, которые не поддерживаются напрямую Room или его библиотеками расширений, вы можете определить пользовательские преобразователи типов возвращаемых значений DAO для поддержки дополнительных типов возвращаемых значений. Чтобы преобразовать результат функции DAO в ваш пользовательский тип, аннотируйте функцию-преобразователь с помощью @DaoReturnTypeConverter .
Например, вы можете определить конвертер, который использует androidx.tracing для добавления разделов трассировки вокруг выполнения запроса, чтобы отслеживать запросы, чувствительные к производительности, путем обертывания выполнения в пользовательский тип TracedQuery :
class TracedQuery<T>(val result: T) object TracingDaoReturnTypeConverter { @DaoReturnTypeConverter([OperationType.READ]) suspend fun <T> convert( rawQuery: RoomRawQuery, executeAndConvert: suspend () -> T ): TracedQuery<T> { val result = trace("TracedQuery: ${rawQuery.sql}") { executeAndConvert() } return TracedQuery(result) } }
Для использования конвертера добавьте аннотацию ` @DaoReturnTypeConverters к вашей базе данных или DAO:
@Dao @DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class) interface MusicDao { @Query("SELECT * FROM Song") suspend fun getAllSongs(): TracedQuery<List<Song>> }
Инициализация преобразователя типа возврата DAO управления
Обычно Room обрабатывает создание экземпляров преобразователей типов возвращаемых значений DAO. Однако, если вам необходимо передать дополнительные зависимости вашим классам преобразователей, ваше приложение должно напрямую контролировать их инициализацию. В этом случае аннотируйте ваш класс преобразователя с помощью @ProvidedDaoReturnTypeConverter :
@ProvidedDaoReturnTypeConverter class TracingDaoReturnTypeConverter(val tracer: Tracer) { @DaoReturnTypeConverter([OperationType.READ]) suspend fun <T> convert( rawQuery: RoomRawQuery, executeAndConvert: suspend () -> T ): TracedQuery<T> { val result = tracer.trace("TracedQuery: ${rawQuery.sql}") { executeAndConvert() } return TracedQuery(result) } }
Затем, помимо объявления класса конвертера в @DaoReturnTypeConverters , используйте функцию RoomDatabase.Builder.addDaoReturnTypeConverter , чтобы передать экземпляр вашего класса конвертера в построитель RoomDatabase :
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance)) .build()
Требования к функциям преобразователя
Функция @DaoReturnTypeConverter должна соответствовать нескольким требованиям:
- В качестве последнего аргумента должен быть функциональный параметр, обычно называемый
executeAndConvert. Этот параметр представляет собой лямбда-suspend, генерируемую Room для выполнения запроса и анализа результата.- Если конвертеру необходимо преобразовать запрос, например, для постраничной навигации, лямбда-функция может принимать параметр
RoomRawQuery.
- Если конвертеру необходимо преобразовать запрос, например, для постраничной навигации, лямбда-функция может принимать параметр
- Перед лямбда-функцией она может дополнительно принимать следующие параметры:
-
db: RoomDatabase: Обращается к экземпляру базы данных, что полезно для получения области видимости сопрограммы или выполнения дополнительных операций. -
tableNames: Array<String>илиList<String>: Предоставляет имена таблиц, к которым обращается запрос, что полезно для наблюдаемых типов. -
rawQuery: RoomRawQuery: Предоставляет экземпляр запроса во время выполнения. -
inTransaction: Boolean: Указывает, выполняется ли запрос в рамках транзакции.
-