Room — це бібліотека для роботи з SQLite в Android, яка входить до складу Jetpack. Вона надає рівень абстракції над сирим SQLite, автоматизуючи створення таблиць, виконання запитів та конвертацію даних в об'єкти Kotlin та Java. За даними Android Developers, Room компілює SQL-запити на етапі збірки, перевіряючи коректність синтаксису та зв'язків між Entity та таблицями.
Головне
Room — це бібліотека персистентності зі складу Android Jetpack, що надає об'єктно-реляційне відображення для SQLite. Room вирішує три основні проблеми сирого SQLite: написання великого обсягу шаблонного коду для створення таблиць, відсутність перевірки SQL-запитів на етапі компіляції та ручне конвертування Cursor в об'єкти.
Бібліотека використовує компілятор анотацій (kapt або KSP), який генерує реалізацію абстрактних класів RoomDatabase та DAO на етапі збірки. Це гарантує, що синтаксичні помилки в SQL та невідповідності типів виявляються до запуску додатка, а не під час виконання після публікації в Google Play.
За даними Google I/O 2023, Room використовується в 68% Android-додатків, які працюють з локальними даними. Це стандарт зберігання даних на пристрої, рекомендований Google для всіх нових проєктів — замість застарілих SQLiteOpenHelper та ContentProvider.
Впроваджуйте Room у проєкти, де потрібне локальне кешування даних з сервера, офлайн-режим або зберігання структурованих користувацьких даних з можливістю складних SQL-запитів.
Room є частиною Android Jetpack та офіційно рекомендований Google для всіх нових проєктів, що працюють з локальними даними. На відміну від Realm або ObjectBox, Room використовує нативний SQLite, що гарантує сумісність з будь-якими сторонніми інструментами для роботи з базою даних — від DB Browser до DataGrip. Розробник може відкрити .db-файл додатка та виконувати SQL-запити безпосередньо, що спрощує налагодження та аналіз даних у процесі розробки.
Entity — це клас даних, анотований @Entity, який Room перетворює на таблицю бази даних. Кожне поле класу стає стовпцем таблиці, а кожен екземпляр — рядком. Room використовує рефлексію для доступу до полів, тому потрібна анотація @PrimaryKey для обов'язкового ідентифікатора.
Анотація @Entity вказує Room, що клас є таблицею. Параметр tableName задає ім'я таблиці, якщо воно відрізняється від імені класу. @PrimaryKey визначає первинний ключ з можливістю автогенерації через autoGenerate = true.
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
@Ignore
val tempData: String?
)
@ColumnInfo задає ім'я стовпця в таблиці, якщо воно відрізняється від імені поля Kotlin. @Ignore виключає поле з таблиці — воно не буде збережене в базу даних. @ForeignKey описує зовнішні ключі для зв'язків між таблицями з каскадними операціями при видаленні або оновленні.
Room підтримує вкладені об'єкти через анотацію @Embedded. Поля вкладеного класу розгортаються в стовпці батьківської таблиці з префіксом для уникнення конфлікту імен. Наприклад, клас Address з полями city та street, вбудований в User, створить стовпці address_city та address_street в таблиці users, позбавляючи від необхідності створення окремих таблиць для простих значень-об'єктів.
Room підтримує лише примітивні типи та їхні обгортки. Для зберігання списків, Date або кастомних типів використовується @TypeConverter — статичні методи конвертації між кастомним типом та примітивом SQLite, наприклад, між List та рядком JSON.
DAO (Data Access Object) — це інтерфейс або абстрактний клас, анотований @Dao, що містить методи для доступу до даних. Кожен метод анотується SQL-операцією: @Insert, @Update, @Delete або @Query з явним SQL-запитом.
Анотація @Query приймає рядок SQL, який перевіряється Room на етапі компіляції на коректність синтаксису та відповідність імен стовпців полям Entity. Room підтримує параметризовані запити через синтаксис :paramName.
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): Flow<List<User>>
}
@Insert підтримує стратегії OnConflictStrategy для обробки конфліктів при вставці дубльованих записів. Flow як тип, що повертається, забезпечує реактивне оновлення UI при кожній зміні даних в таблиці — підписка автоматично перезапускається при будь-якому INSERT, UPDATE або DELETE.
Анотація @Transaction гарантує атомарне виконання кількох операцій в одному транзакційному блоці. Room блокує базу даних на час виконання, запобігаючи стану гонки при конкурентному доступі з кількох потоків.
RoomDatabase — абстрактний клас, який об'єднує Entity та DAO в єдину точку доступу до бази даних. Він створюється через Room.databaseBuilder із зазначенням версії схеми та списку Entity-класів. Екземпляр бази даних рекомендується створювати як синглтон через lazy-делегат для уникнення множинних підключень.
Міграція в Room — це клас Migration, що описує SQL-скрипт для переходу від старої версії схеми до нової. Якщо міграція не надана при зміні схеми, Room падає з IllegalStateException. Це захищає від випадкової втрати даних користувача при оновленні додатка.
val migration_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
val db = Room.databaseBuilder(
getApplication(),
AppDatabase::class.java,
"app_database"
).addMigrations(migration_1_2)
.build()
Для розробки можна використовувати fallbackToDestructiveMigration, який видаляє стару базу даних та створює нову при неспівпадінні версій. Цей режим призначений лише для налагодження — в продакшн-релізах обов'язково пишуться міграції.
Для тестування бази даних Room надає спеціальний клас Room.inMemoryTestBuilder, який створює базу даних в оперативній пам'яті без збереження на диск. Після завершення кожного тесту база даних автоматично знищується, що гарантує повну ізоляцію тестових сценаріїв. У поєднанні з бібліотекою android-arch-core-testing розробник може керувати життєвим циклом бази даних та перевіряти коректність міграцій без необхідності очищати стан вручну.
Продуктивність Room безпосередньо залежить від структури запитів та індексів. Для аналізу повільних запитів Room надає прапорець enableQueryCallback, який логує всі SQL-запити з часом виконання. Розробник може використовувати цей лог для пошуку запитів, що виконуються довше 100 мілісекунд, та оптимізувати їх додаванням складених індексів через анотацію @Index в @Entity або переписуванням підзапитів на прямі JOIN-з'єднання з використанням @Relation.
Room також підтримує шифрування бази даних через SQLCipher. Підключення бібліотеки net.zetetic:android-database-sqlcipher та використання SupportFactory замість стандартного забезпечує прозоре шифрування всіх даних на диску без зміни DAO-запитів та структури Entity. Це необхідно для додатків, що працюють з персональними даними користувачів, і відповідає вимогам GDPR та російського 152-ФЗ про захист персональних даних. Пароль шифрування може зберігатися в Android Keystore для захисту від вилучення через інструментарій на рутованих пристроях.
Room нативно підтримує Kotlin Coroutines починаючи з версії 2.1. DAO-методи можуть бути suspend-функціями, що виконують запити у фоновому потоці без блокування головного. Room автоматично керує диспетчерами, використовуючи Dispatchers.IO для запитів читання та запису.
Для реактивних запитів Room повертає Flow — холодний потік даних, який емітує нове значення при кожній зміні задіяної таблиці. ViewModel підписується на Flow через stateIn або collect, забезпечуючи автоматичне оновлення UI без ручного сповіщення адаптера.
Room підтримує також Paging 3 через спеціальну реалізацію PagingSource, яка завантажує дані посторінково з SQLite. Це ефективно для великих списків з тисячами записів: Paging 3 завантажує лише видимі на екрані рядки та автоматично оновлює їх при змінах у базі даних.
Використовуйте Paging 3 з Room при відображенні стрічки новин, лога операцій або списку товарів з можливістю офлайн-доступу та нескінченною прокруткою.
Часті запитання
Room автоматизує створення таблиць, конвертацію Cursor в об'єкти та перевірку SQL на етапі компіляції. SQLiteOpenHelper вимагає писати схему вручну, обробляти Cursor і не має перевірки запитів до запуску додатка, що збільшує ризик помилок.
Так, при зміні Entity (додавання/видалення поля, зміна типу) потрібна міграція. Без неї Room викидає IllegalStateException при запуску. Для розробки можна включити fallbackToDestructiveMigration, але в релізі обов'язкові коректні скрипти міграції.
Room підтримує @ForeignKey для каскадних операцій та @Relation для вкладених об'єктів. Для складних JOIN-запитів використовується анотація @Transaction з @Query, що повертає POJO з вкладеними сутностями через @Embedded та @Relation.
Так, Room повністю сумісний з Java. Замість suspend-функцій використовуються LiveData або RxJava Observable, замість Flow — LiveData. Room з Java підтримує всі ті ж анотації, але вимагає більше шаблонного коду для асинхронних операцій.
Room підтримує шифрування через SQLCipher від Zetetic. Замість Room.databaseBuilder використовуйте SupportFactory з бібліотеки net.zetetic:android-database-sqlcipher, передавши пароль шифрування. Всі дані на диску будуть зашифровані прозоро для DAO-запитів.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.