# Room Database в Android 2026: Міграції, Зв'язки та Coroutines > Повний посібник з Room Database в Android 2026 - від міграцій схеми, через зв'язки між сутностями, до інтеграції з Kotlin Coroutines та найкращих практик. - Published: 2026-07-10 - Updated: 2026-07-10 - Author: SharpSkill - Tags: android, room, kotlin, coroutines, database - Reading time: 12 min --- Room Database залишається фундаментальним інструментом для локального зберігання даних у додатках Android. У 2026 році ця бібліотека пропонує ще більше можливостей, включаючи покращену підтримку міграцій, розширені зв'язки між сутностями та повну інтеграцію з Kotlin Coroutines. Ця стаття представляє комплексний підхід до використання Room у сучасних проектах Android. > Room Database є частиною Android Jetpack і становить рівень абстракції над SQLite, усуваючи потребу писати шаблонний код та забезпечуючи перевірку SQL-запитів під час компіляції. ## Налаштування Room у проекті Android 2026 Для початку роботи з Room необхідно додати відповідні залежності до проекту. У 2026 році рекомендується використовувати найновіші версії бібліотеки разом з KSP (Kotlin Symbol Processing) для кращої продуктивності компіляції. ```kotlin // build.gradle.kts (Module) plugins { id("com.google.devtools.ksp") version "2.1.0-1.0.29" } dependencies { val roomVersion = "2.7.0" implementation("androidx.room:room-runtime:$roomVersion") implementation("androidx.room:room-ktx:$roomVersion") ksp("androidx.room:room-compiler:$roomVersion") } ``` Після налаштування залежностей можна приступати до визначення сутностей, які представляють таблиці в базі даних. ## Визначення сутностей та базових операцій Сутності в Room — це класи даних, позначені анотацією `@Entity`. Кожна сутність відповідає таблиці в базі SQLite. ```kotlin @Entity(tableName = "users") data class User( @PrimaryKey(autoGenerate = true) val id: Long = 0, @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String, @ColumnInfo(name = "email") val email: String, @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis() ) ``` DAO (Data Access Object) визначає методи доступу до даних. Room автоматично генерує реалізацію цих інтерфейсів. ```kotlin @Dao interface UserDao { @Query("SELECT * FROM users ORDER BY created_at DESC") suspend fun getAllUsers(): List @Query("SELECT * FROM users WHERE id = :userId") suspend fun getUserById(userId: Long): User? @Query("SELECT * FROM users WHERE email = :email LIMIT 1") suspend fun getUserByEmail(email: String): User? @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUser(user: User): Long @Update suspend fun updateUser(user: User) @Delete suspend fun deleteUser(user: User) @Query("DELETE FROM users") suspend fun deleteAllUsers() } ``` ## Міграції схеми бази даних Міграції є ключовим елементом управління еволюцією схеми бази даних. Room вимагає явного визначення міграцій при кожній зміні версії бази. ```kotlin val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE users ADD COLUMN phone_number TEXT") } } val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL(""" CREATE TABLE IF NOT EXISTS user_preferences ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER NOT NULL, theme TEXT NOT NULL DEFAULT 'system', notifications_enabled INTEGER NOT NULL DEFAULT 1, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) """.trimIndent()) db.execSQL("CREATE INDEX index_user_preferences_user_id ON user_preferences(user_id)") } } ``` У випадку складних міграцій, таких як зміна типу колонки або реорганізація даних, необхідно застосувати стратегію з тимчасовою таблицею. ```kotlin val MIGRATION_3_4 = object : Migration(3, 4) { override fun migrate(db: SupportSQLiteDatabase) { // Створення нової таблиці з виправленою схемою db.execSQL(""" CREATE TABLE users_new ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, first_name TEXT NOT NULL, last_name TEXT NOT NULL, email TEXT NOT NULL, phone_number TEXT, created_at INTEGER NOT NULL, is_verified INTEGER NOT NULL DEFAULT 0 ) """.trimIndent()) // Копіювання даних зі старої таблиці db.execSQL(""" INSERT INTO users_new (id, first_name, last_name, email, phone_number, created_at) SELECT id, first_name, last_name, email, phone_number, created_at FROM users """.trimIndent()) // Видалення старої таблиці db.execSQL("DROP TABLE users") // Перейменування нової таблиці db.execSQL("ALTER TABLE users_new RENAME TO users") // Відновлення індексів db.execSQL("CREATE UNIQUE INDEX index_users_email ON users(email)") } } ``` ## Автоматичні міграції в Room 2.7 Room 2.7 впроваджує спрощені автоматичні міграції для простих змін схеми. Система автоматично виявляє додавання нових колонок або таблиць. ```kotlin @Database( version = 5, entities = [User::class, UserPreferences::class, Article::class], autoMigrations = [ AutoMigration(from = 4, to = 5) ], exportSchema = true ) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun articleDao(): ArticleDao } ``` Для складніших автоматичних міграцій можна визначити специфікацію міграції. ```kotlin @Database( version = 6, entities = [User::class, UserPreferences::class, Article::class], autoMigrations = [ AutoMigration(from = 5, to = 6, spec = Migration5To6::class) ] ) abstract class AppDatabase : RoomDatabase() { @RenameColumn(tableName = "users", fromColumnName = "phone_number", toColumnName = "mobile_phone") @DeleteColumn(tableName = "users", columnName = "legacy_field") class Migration5To6 : AutoMigrationSpec } ``` ## Зв'язки між сутностями Room підтримує різні типи зв'язків між сутностями: один-до-одного, один-до-багатьох та багато-до-багатьох. ### Зв'язок один-до-багатьох ```kotlin @Entity(tableName = "articles") data class Article( @PrimaryKey(autoGenerate = true) val id: Long = 0, val authorId: Long, val title: String, val content: String, val publishedAt: Long = System.currentTimeMillis() ) data class UserWithArticles( @Embedded val user: User, @Relation( parentColumn = "id", entityColumn = "authorId" ) val articles: List
) @Dao interface UserDao { @Transaction @Query("SELECT * FROM users WHERE id = :userId") suspend fun getUserWithArticles(userId: Long): UserWithArticles? @Transaction @Query("SELECT * FROM users") fun getAllUsersWithArticles(): Flow> } ``` ### Зв'язок багато-до-багатьох ```kotlin @Entity(tableName = "tags") data class Tag( @PrimaryKey(autoGenerate = true) val tagId: Long = 0, val name: String ) @Entity( tableName = "article_tag_cross_ref", primaryKeys = ["articleId", "tagId"], foreignKeys = [ ForeignKey( entity = Article::class, parentColumns = ["id"], childColumns = ["articleId"], onDelete = ForeignKey.CASCADE ), ForeignKey( entity = Tag::class, parentColumns = ["tagId"], childColumns = ["tagId"], onDelete = ForeignKey.CASCADE ) ] ) data class ArticleTagCrossRef( val articleId: Long, val tagId: Long ) data class ArticleWithTags( @Embedded val article: Article, @Relation( parentColumn = "id", entityColumn = "tagId", associateBy = Junction(ArticleTagCrossRef::class) ) val tags: List ) ``` ## Інтеграція з Kotlin Coroutines та Flow Room пропонує нативну інтеграцію з Kotlin Coroutines, дозволяючи реактивно спостерігати за змінами в базі даних. ```kotlin @Dao interface ArticleDao { // Одноразове отримання даних @Query("SELECT * FROM articles ORDER BY publishedAt DESC") suspend fun getAllArticles(): List
// Реактивний Flow - автоматично емітує при змінах @Query("SELECT * FROM articles ORDER BY publishedAt DESC") fun observeAllArticles(): Flow> // Flow з параметром @Query("SELECT * FROM articles WHERE authorId = :authorId ORDER BY publishedAt DESC") fun observeArticlesByAuthor(authorId: Long): Flow> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertArticle(article: Article): Long @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertArticles(articles: List
) @Query("DELETE FROM articles WHERE id = :articleId") suspend fun deleteArticleById(articleId: Long) } ``` У ViewModel дані з Flow можна перетворити в StateFlow для легшого управління станом UI. ```kotlin class ArticleViewModel( private val articleDao: ArticleDao ) : ViewModel() { val articles: StateFlow> = articleDao .observeAllArticles() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading.asStateFlow() fun addArticle(title: String, content: String, authorId: Long) { viewModelScope.launch { _isLoading.value = true try { val article = Article( title = title, content = content, authorId = authorId ) articleDao.insertArticle(article) } finally { _isLoading.value = false } } } } ``` ## Транзакції та пакетні операції Room підтримує транзакції для операцій, що вимагають атомарності. Анотація `@Transaction` забезпечує виконання всіх операцій в одній транзакції. ```kotlin @Dao interface ArticleDao { @Transaction suspend fun replaceAllArticles(articles: List
) { deleteAllArticles() insertArticles(articles) } @Query("DELETE FROM articles") suspend fun deleteAllArticles() @Insert suspend fun insertArticles(articles: List
) } ``` Для складніших сценаріїв можна використовувати `withTransaction` з об'єкта бази даних. ```kotlin class ArticleRepository( private val database: AppDatabase, private val articleDao: ArticleDao, private val tagDao: TagDao ) { suspend fun createArticleWithTags( article: Article, tagNames: List ): Long = database.withTransaction { val articleId = articleDao.insertArticle(article) tagNames.forEach { tagName -> val existingTag = tagDao.getTagByName(tagName) val tagId = existingTag?.tagId ?: tagDao.insertTag(Tag(name = tagName)) tagDao.insertArticleTagCrossRef( ArticleTagCrossRef(articleId = articleId, tagId = tagId) ) } articleId } } ``` ## Тестування Room Database Room пропонує підтримку тестування з використанням бази даних у пам'яті. ```kotlin @RunWith(AndroidJUnit4::class) class UserDaoTest { private lateinit var database: AppDatabase private lateinit var userDao: UserDao @Before fun setup() { val context = ApplicationProvider.getApplicationContext() database = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java) .allowMainThreadQueries() .build() userDao = database.userDao() } @After fun teardown() { database.close() } @Test fun insertAndRetrieveUser() = runTest { val user = User( firstName = "Олександр", lastName = "Петренко", email = "oleksandr@example.com" ) val userId = userDao.insertUser(user) val retrievedUser = userDao.getUserById(userId) assertThat(retrievedUser).isNotNull() assertThat(retrievedUser?.firstName).isEqualTo("Олександр") assertThat(retrievedUser?.email).isEqualTo("oleksandr@example.com") } @Test fun testMigration() = runTest { val helper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), AppDatabase::class.java ) // Створення бази у версії 1 helper.createDatabase(TEST_DB, 1).apply { execSQL("INSERT INTO users (first_name, last_name, email, created_at) VALUES ('Test', 'User', 'test@example.com', 0)") close() } // Міграція до версії 2 helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2) } companion object { private const val TEST_DB = "test-database" } } ``` ## Найчастіші питання на співбесідах щодо Room Під час співбесід на позиції Android Developer часто з'являються питання про Room Database: **Чим Room відрізняється від SQLite?** Room є рівнем абстракції над SQLite, який пропонує перевірку SQL-запитів під час компіляції, автоматичну генерацію коду DAO та нативну інтеграцію з Kotlin Coroutines і LiveData/Flow. **Як Room обробляє багатопотоковість?** Room за замовчуванням вимагає виконання операцій з базою даних поза головним потоком. Завдяки інтеграції з Coroutines методи DAO, позначені як `suspend`, автоматично виконуються у відповідному контексті. **Коли використовувати автоматичні міграції, а коли ручні?** Автоматичні міграції підходять для простих змін, як-от додавання колонки або таблиці. Ручні міграції необхідні при зміні типу даних, реорганізації таблиць або трансформації існуючих даних. ## Висновок Room Database у 2026 році пропонує зрілі та стабільні рішення для локального зберігання даних у додатках Android. Ключові аспекти включають правильне управління міграціями схеми, ефективне моделювання зв'язків між сутностями та використання Kotlin Coroutines для реактивного доступу до даних. Знання цих питань є необхідним для кожного розробника Android, який працює над додатками, що вимагають локальної бази даних. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/android/room-database-android-2026-migrations-relations-coroutines