Как сделать запрос к БД в Room
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Работа с Room: запросы к базе данных
Для выполнения запросов к базе данных в библиотеке Room существует несколько механизмов, которые являются частью паттерна Repository и следуют рекомендациям Android Architecture Components. Я рассмотрю ключевые подходы: использование DAO (Data Access Object), типы возвращаемых данных и асинхронное выполнение.
1. Создание DAO интерфейса
DAO определяет методы доступа к базе данных с использованием аннотаций SQL. Это центральный компонент для запросов.
@Dao
interface UserDao {
// Простой запрос: выбор всех записей
@Query("SELECT * FROM users")
suspend fun getAll(): List<User>
// Запрос с параметром
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getById(userId: Long): User?
// Запрос, возвращающий не entity, а другую модель (например, для JOIN)
@Query("SELECT u.name, p.title FROM users u INNER JOIN posts p ON u.id = p.userId")
fun getUsersWithPosts(): List<UserPost>
// Запрос с несколькими параметрами
@Query("SELECT * FROM users WHERE age BETWEEN :minAge AND :maxAge")
suspend fun getUsersByAgeRange(minAge: Int, maxAge: Int): List<User>
// Неблокирующий запрос с Flow для наблюдения за изменениями
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsersStream(): Flow<List<User>>
// Агрегатные функции
@Query("SELECT COUNT(*) FROM users")
suspend fun getCount(): Int
}
2. Типы возвращаемых данных
Room поддерживает различные типы возвращаемых данных:
- Entity объекты - базовый случай, возвращает таблицу в виде классов
- Пользовательские POJO - для сложных запросов с объединением таблиц
- LiveData - для наблюдения за изменениями с автоматическим обновлением UI
- Flow - корутиновый аналог LiveData, современный стандарт
- RxJava типы (Single, Observable) - для проектов с RxJava
- Cursor - низкоуровневый доступ (редко используется)
3. Асинхронное выполнение запросов
Поскольку запросы к БД — блокирующие операции, их нельзя выполнять в главном потоке:
С корутинами (рекомендуемый подход):
class UserRepository(private val userDao: UserDao) {
// Suspend функции автоматически выполняются в фоновом потоке
suspend fun getUsers(): List<User> {
return userDao.getAll()
}
// Flow предоставляет реактивный поток данных
fun getUsersStream(): Flow<List<User>> {
return userDao.getAllUsersStream()
}
}
// Использование в ViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val users: Flow<List<User>> = repository.getUsersStream()
fun loadUsers() {
viewModelScope.launch {
val usersList = repository.getUsers()
// Обработка результата
}
}
}
С LiveData:
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): LiveData<List<User>>
}
// LiveData автоматически уведомляет наблюдателей об изменениях
userDao.getAllUsers().observe(viewLifecycleOwner) { users ->
// Обновление UI
}
4. Пагинация с Paging 3.0
Для работы с большими наборами данных используйте библиотеку Paging:
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY name")
fun getUsersPaged(): PagingSource<Int, User>
}
// Создание Pager в Repository
class UserRepository(private val dao: UserDao) {
fun getUsersPaged(): Flow<PagingData<User>> {
return Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { dao.getUsersPaged() }
).flow
}
}
5. Транзакции и сложные операции
Для атомарных операций используйте @Transaction:
@Dao
interface UserDao {
@Transaction
suspend fun updateUserWithLog(oldUser: User, newUser: User) {
updateUser(newUser)
insertLog(ChangeLog("User updated", oldUser, newUser))
}
@Query("UPDATE users SET name = :name WHERE id = :id")
suspend fun updateUser(name: String, id: Long)
}
6. Практические рекомендации
- Всегда выполняйте запросы вне главного потока - Room генерирует предупреждение для синхронных вызовов из UI-потока
- Используйте индексы для оптимизации частых запросов по определенным полям
- Минимизируйте количество возвращаемых данных - выбирайте только необходимые колонки
- Кэшируйте результаты при частом чтении одних и тех же данных
- Логируйте запросы в отладочных сборках через
RoomDatabase.Builder.setQueryCallback()
Пример полной реализации
// Entity
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val name: String,
val age: Int,
@ColumnInfo(name = "created_at") val createdAt: Long
)
// DAO
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User)
@Query("SELECT * FROM users WHERE age > :minAge")
fun getAdultUsers(minAge: Int = 18): Flow<List<User>>
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
}
// Repository
class UserRepository(private val userDao: UserDao) {
val adultUsers: Flow<List<User>> = userDao.getAdultUsers()
suspend fun addUser(name: String, age: Int) {
val user = User(name = name, age = age, createdAt = System.currentTimeMillis())
userDao.insert(user)
}
}
Room предоставляет типобезопасный, компиляционно-проверяемый подход к работе с SQLite, что значительно уменьшает количество runtime-ошибок. Комбинация suspend функций, Flow и Paging 3.0 позволяет создавать эффективные и отзывчивые приложения с оптимальным управлением памятью и производительностью.