← Назад к вопросам

Как сделать запрос к БД в Room

1.6 Junior🔥 271 комментариев
#Архитектура и паттерны#Работа с данными

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Работа с 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. Практические рекомендации

  1. Всегда выполняйте запросы вне главного потока - Room генерирует предупреждение для синхронных вызовов из UI-потока
  2. Используйте индексы для оптимизации частых запросов по определенным полям
  3. Минимизируйте количество возвращаемых данных - выбирайте только необходимые колонки
  4. Кэшируйте результаты при частом чтении одних и тех же данных
  5. Логируйте запросы в отладочных сборках через 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 позволяет создавать эффективные и отзывчивые приложения с оптимальным управлением памятью и производительностью.

Как сделать запрос к БД в Room | PrepBro