Room — это ORM-библиотека из состава Android Jetpack, которая предоставляет слой абстракции над SQLite для работы с локальными базами данных на Android. По данным официальной документации Android Developers, 2025, Room автоматически генерирует реализации DAO на основе аннотаций во время компиляции, что устраняет около 70% шаблонного кода по сравнению с прямым использованием SQLiteOpenHelper. Библиотека выполняет проверку SQL-запросов на этапе компиляции, что позволяет выявить синтаксические ошибки до запуска приложения на устройстве.
Главное
Room — это ORM-библиотека из состава Android Jetpack, созданная Google для упрощения работы с локальными базами данных SQLite на платформе Android. Она предоставляет аннотации для описания схемы данных и автоматически генерирует реализацию DAO-интерфейсов на этапе компиляции. В отличие от прямого использования SQLiteOpenHelper, Room избавляет разработчика от написания значительного объёма boilerplate-кода для создания, открытия и управления соединением с базой данных.
Библиотека была представлена на Google I/O 2017 как часть архитектурных компонентов Android. С тех пор Room стала стандартом де-факто для локального хранения данных, обогнав по популярности такие решения, как GreenDAO и Realm для Android. По данным Google, библиотека используется более чем в 60% приложений, опубликованных в Google Play, которые работают с локальными данными на устройстве.
Ключевая особенность — проверка SQL-запросов на этапе компиляции с помощью процессора аннотаций. Если разработчик допустил ошибку в SQL-команде, например, указал несуществующее имя столбца, сборка завершится с ошибкой до установки приложения. Это кардинально отличается от подхода SQLiteOpenHelper, где такие ошибки обнаруживаются только во время выполнения, часто в продакшне.
SQLite поддерживает только пять типов данных: TEXT, INTEGER, REAL, BLOB и NULL. Однако в Java и Kotlin используются сложные типы: Date, List, Enum и кастомные объекты. Для их сохранения Room предоставляет механизм TypeConverters — статических методов, преобразующих сложный тип в примитивный, понятный SQLite. Например, объект Date конвертируется в Long (timestamp), а List<String> — в JSON-строку через Gson или Moshi.
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.build()
Для объявления конвертера достаточно добавить аннотацию @TypeConverter к статическому методу и указать класс конвертера в аннотации @TypeConverters на уровне базы данных. Room автоматически применяет конвертер при чтении и записи соответствующего типа в каждом SQL-запросе без ручного вызова методов преобразования.
Room состоит из трёх основных компонентов: Entity, DAO и Database. Каждый выполняет строго определённую роль и аннотируется соответствующей аннотацией. Совместно они образуют полноценный слой доступа к данным, который изолирует бизнес-логику приложения от деталей реализации SQLite.
Entity — это класс данных, описывающий структуру одной таблицы в базе. Каждое поле класса соответствует столбцу таблицы, а каждая строка в базе — одному экземпляру класса. Аннотация @Entity указывает Room, что класс является таблицей. Поле с аннотацией @PrimaryKey задаёт первичный ключ, который может быть автоинкрементным или составным. Для связи между таблицами используется @ForeignKey, обеспечивающая целостность данных на уровне базы.
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
val age: Int,
val email: String
)
DAO (Data Access Object) — это интерфейс или абстрактный класс, объявляющий операции для работы с данными: вставку, чтение, обновление и удаление. Каждая операция аннотируется @Insert, @Query, @Update или @Delete. Room автоматически генерирует реализацию этого интерфейса на этапе компиляции. Особую ценность представляет аннотация @Query, которая принимает SQL-запрос в виде строки и проверяет его корректность на этапе сборки.
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User): Long
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
@Delete
suspend fun delete(user: User)
}
Database — это абстрактный класс, наследующий RoomDatabase, служащий точкой входа в базу данных. Он содержит список всех Entity и предоставляет абстрактные методы для получения DAO. Класс аннотируется @Database, где указываются версия схемы и список сущностей. Создание экземпляра базы выполняется через Room.databaseBuilder с указанием контекста приложения, имени файла и класса Database.
Room не заменяет SQLite, а работает поверх него как слой абстракции. Внутренняя архитектура включает процессор аннотаций, генератор кода и пул соединений. На этапе компиляции процессор аннотаций анализирует классы Entity, DAO и Database, после чего генерирует классы-реализации с суффиксом _Impl. Все сгенерированные классы помещаются в пакет сборки и не видны разработчику напрямую.
Генерация кода на этапе компиляции — центральный механизм Room. Для каждого DAO-интерфейса генерируется класс с полной реализацией всех аннотированных методов. SQL-запросы из аннотации @Query проверяются на корректность: процессор сопоставляет имена столбцов с полями Entity и проверяет синтаксис SQL. При обнаружении ошибки сборка прерывается с понятным сообщением. Это невозможно при использовании сырого SQLiteOpenHelper, где ошибки проявляются только в runtime.
Процесс генерации включает три этапа. Первый — валидация схемы: процессор проверяет, что все классы, перечисленные в @Database, являются корректными Entity. Второй — генерация тела DAO: для каждого метода создаётся реализация с использованием внутреннего объекта RoomSQLiteQuery, выполняющего prepared-запросы. Третий — генерация класса Database_Impl, реализующего создание и открытие базы, а также инициализацию всех DAO-объектов.
class UserDao_Impl(private val __db: RoomDatabase) : UserDao {
private val __insertionAdapter = __db
.createInsertionAdapter(User::class, 0)
override suspend fun insert(user: User): Long {
__db.assertNotSuspendingTransaction()
return __db.runInTransaction {
__insertionAdapter.insertAndReturnId(user)
}
}
}
Room не создаёт отдельный пул потоков для операций с базой. По умолчанию запросы выполняются в вызывающем потоке с одним ограничением: чтение и запись блокируют поток. Для асинхронной работы Room интегрируется с корутинами Kotlin через suspend-функции, с LiveData через возвращаемые значения и с Flow через реактивные обёртки. Это даёт разработчику гибкость выбора архитектурного решения под конкретную задачу.
Рассмотрим практический пример создания приложения для хранения заметок с использованием Room. Приложение содержит одну таблицу Note с полями id, title, content и timestamp. Пользователь сможет добавлять, просматривать и удалять заметки. Для демонстрации применяются корутины для асинхронных операций.
Для подключения Room в проект Android необходимо добавить зависимости в файл build.gradle модуля приложения. Room требует три компонента: runtime-библиотеку, процессор аннотаций kapt и опциональную поддержку корутин. Версия библиотеки указывается в переменной room_version для удобства обновления. Начиная с Room 2.4.0, поддерживается KSP как альтернатива kapt с более высокой скоростью сборки.
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// Опционально: тестирование
testImplementation "androidx.room:room-testing:$room_version"
}
После настройки зависимостей создаются три файла: Note Entity, NoteDao интерфейс и AppDatabase класс. Entity Note содержит поля с аннотациями @PrimaryKey и @ColumnInfo. DAO предоставляет методы для вставки, получения списка и удаления. Database связывает Entity и DAO через аннотацию @Database.
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String,
@ColumnInfo(name = "created_at")
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface NoteDao {
@Insert
suspend fun insert(note: Note)
@Query("SELECT * FROM notes ORDER BY created_at DESC")
fun getAllNotes(): Flow<List<Note>>
@Delete
suspend fun delete(note: Note)
}
Файл AppDatabase объявляется как абстрактный класс, наследующий RoomDatabase. В аннотации @Database указываются все Entity текущей версии и номер версии схемы. Для получения экземпляра используется паттерн синглтон через build-метод Room.databaseBuilder с контекстом приложения. Кэширование экземпляра базы предотвращает множественные создания, которые могут привести к утечкам памяти.
Миграции в Room — это механизм для изменения схемы базы данных при обновлении приложения без потери существующих данных. Когда пользователь устанавливает новую версию с изменёнными Entity, Room обнаруживает несоответствие версий и выполняет указанные миграционные шаги. Без миграции база данных будет удалена и создана заново, что приведёт к потере всех сохранённых пользователем данных.
Миграция описывается классом Migration, принимающим начальную и конечную версию базы. Внутри метода migrate выполняется SQL-запрос ALTER TABLE или CREATE TABLE для изменения схемы. Room не умеет автоматически определять изменения схемы — разработчик обязан написать миграцию вручную для каждого изменения Entity. Начиная с версии Room 2.4.0, доступна экспериментальная функция autoMigrations для автоматической генерации миграций.
Функция autoMigrations позволяет Room автоматически генерировать миграции на основе различий между версиями Entity. Для её использования достаточно добавить аннотацию @AutoMigration в @Database и указать экспорт схемы в JSON. Room сравнивает схемы соседних версий и генерирует необходимые ALTER-запросы. Однако autoMigrations поддерживает только обратно совместимые изменения: добавление колонок, создание индексов и изменение типов с совместимыми преобразованиями.
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE users ADD COLUMN phone TEXT"
)
}
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.addMigrations(MIGRATION_1_2)
.build()
При добавлении сложных изменений, таких как переименование колонок или объединение таблиц, требуется ручная миграция с использованием промежуточных таблиц. Типичный сценарий: создать временную таблицу со старой схемой, скопировать данные из старой таблицы в новую с преобразованиями, удалить старую таблицу и переименовать временную. Room гарантирует, что все миграции выполняются в одной транзакции, и при возникновении ошибки изменения откатываются полностью.
Часто задаваемые вопросы
Room предоставляет ORM-абстракцию с аннотациями и проверкой SQL на этапе компиляции, тогда как SQLiteOpenHelper требует ручного написания всех запросов и управления соединением. Room автоматически генерирует код для CRUD-операций и интегрируется с архитектурными компонентами Android, включая LiveData и Flow.
Room поддерживает все примитивные типы Java: Int, Long, Boolean, Float, Double, а также String, ByteArray и Date. Для сложных типов, таких как List или Enum, используются TypeConverters — статические методы преобразования, которые конвертируют нестандартные типы в поддерживаемые SQLite форматы.
Да, Room поддерживает синхронные вызовы без корутин, но они блокируют поток, в котором выполняются. Для асинхронной работы можно использовать LiveData или RxJava вместо корутин. Google рекомендует применять корутины как основной способ асинхронного доступа к данным в новых проектах.
Если Room обнаруживает несоответствие версии базы данных и не находит подходящей миграции, по умолчанию возникает IllegalStateException с описанием ошибки. Разработчик может переопределить это поведение методом fallbackToDestructiveMigration, который удалит существующую базу и создаст новую с потерей всех данных.
Room поддерживает связи через вложенные объекты с аннотацией @Embedded и через классы отношений с аннотацией @Relation. Для сложных запросов с объединением таблиц используются кастомные POJO-классы, поля которых заполняются из результатов @Query с SQL-оператором JOIN.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также