Пользователь обновил приложение — и при первом запуске видит белый экран или крэш. В Logcat: IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. Или хуже: Migration didn't properly handle с потерей всех локальных данных. Это классика неправильно реализованной миграции Room. Наш опыт — 7+ лет в Android-разработке, более 30 проектов с миграциями разной сложности. Мы поможем избежать таких ситуаций. В этой статье разберем, как правильно выполнить миграцию схемы базы данных Room, какие типы изменений бывают и как их тестировать.
По статистике, около 30% проектов сталкиваются с ошибками миграции, приводящими к потере данных. Своевременное тестирование снижает этот риск на 80%. Правильная миграция экономит до 50% времени на отладку.
Room миграция: как избежать ошибок?
Как Room определяет необходимость миграции?
Room хранит хэш схемы базы данных. При каждом запуске сравнивает хэш скомпилированного @Database с хэшем, хранящимся в room_master_table. Если они не совпадают — Room бросает исключение, если не найдена подходящая миграция. version в @Database — это контракт: если схема изменилась, version должен быть увеличен, и добавлена явная Migration(fromVersion, toVersion). Room управляет версионированием автоматически, но только при условии корректно описанных переходов.
Типы изменений и их миграция
Добавление колонки (простой случай)
val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE transactions ADD COLUMN category TEXT NOT NULL DEFAULT ''") } } NOT NULL DEFAULT '' — обязательно. SQLite не позволяет добавить NOT NULL колонку без DEFAULT в уже существующую таблицу с данными.
Переименование колонки
SQLite не поддерживает ALTER TABLE RENAME COLUMN до версии 3.25.0. На Android API < 29 это недоступно. Универсальный путь — пересоздание таблицы:
val MIGRATION_2_3 = object : Migration(2, 3) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL(""" CREATE TABLE transactions_new ( id TEXT NOT NULL PRIMARY KEY, amount REAL NOT NULL, description TEXT NOT NULL DEFAULT '', created_at INTEGER NOT NULL ) """) db.execSQL(""" INSERT INTO transactions_new (id, amount, description, created_at) SELECT id, amount, note, created_at FROM transactions """) db.execSQL("DROP TABLE transactions") db.execSQL("ALTER TABLE transactions_new RENAME TO transactions") } } Добавление таблицы с внешним ключом
Создайте таблицы через CREATE TABLE IF NOT EXISTS. Внешние ключи включаются после завершения миграции — Room управляет foreign_keys автоматически.
Что такое цепочки миграций и как их использовать?
Room может применять миграции последовательно. Если пользователь перескочил с версии 1 на 4, Room выполнит MIGRATION_1_2, затем MIGRATION_2_3, затем MIGRATION_3_4 — при условии, что все они зарегистрированы.
Room.databaseBuilder(context, AppDatabase::class.java, "app.db") .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4) .build() Для ускорения можно добавить прямой Migration(1,4), выполняющий все изменения за один проход.
Тестирование миграций через MigrationTestHelper
Каждую миграцию необходимо тестировать. Room предоставляет MigrationTestHelper для JUnit. Инструмент позволяет автоматизировать проверку в 10 раз быстрее, чем ручное тестирование. Пример для миграции 1→2:
@RunWith(AndroidJUnit4::class) class MigrationTest { @get:Rule val helper = MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), AppDatabase::class.java ) @Test fun migrate1To2() { helper.createDatabase(TEST_DB, 1).apply { execSQL("INSERT INTO transactions VALUES ('id1', 100.0, 'test', 1700000000)") close() } val db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2) val cursor = db.query("SELECT category FROM transactions WHERE id = 'id1'") assertTrue(cursor.moveToFirst()) assertEquals("", cursor.getString(0)) } } Без тестов вы рискуете потерять пользовательские данные. Мы всегда включаем тесты в объем работ.
Экспорт JSON-схем для валидации
Добавьте в build.gradle аннотацию:
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] } } } } Room генерирует schemas/1.json, schemas/2.json — снимки схемы каждой версии. Эти файлы нужно коммитить в репозиторий. Без них MigrationTestHelper не сможет валидировать миграции.
| Тип изменения | Описание | Риск потери данных | Необходимость теста |
|---|---|---|---|
| Добавление колонки | ALTER TABLE ADD COLUMN | Низкий | Да |
| Переименование колонки | Пересоздание таблицы | Средний | Обязателен |
| Добавление таблицы | CREATE TABLE | Низкий | Да |
| Удаление колонки | Пересоздание таблицы | Высокий | Обязателен |
| Destructive migration | Database.delete() | Полная | Нет (не для production) |
Сравнение стратегий миграции
| Стратегия | Скорость | Надёжность | Сложность |
|---|---|---|---|
| ALTER TABLE ADD COLUMN | Мгновенно | Высокая | Низкая |
| Пересоздание таблицы | Средняя | Высокая | Средняя |
| Destructive migration | Мгновенно | Низкая | Нулевая |
Как избежать потери данных при миграции?
Всегда тестируйте каждую миграцию на реальных или синтетических данных. Используйте полный набор кейсов: пустые таблицы, записи с NULL, дубликаты. Обязательно проверяйте индексы и триггеры после миграции. Дополнительно рекомендуем делать резервное копирование перед обновлением.
Fallback на destructive migration
В крайнем случае — только для debug-сборок или при явном согласии пользователя: fallbackToDestructiveMigration() стирает все данные. В production это недопустимо.
Миграция схемы Room: пошаговая настройка
- Определите изменения схемы и увеличьте version в
@Database. - Напишите объект Migration с SQL-командами.
- Зарегистрируйте миграцию в
addMigrations(). - Настройте экспорт JSON-схем в build.gradle.
- Создайте JUnit-тест с MigrationTestHelper.
- Запустите тест и убедитесь в корректности.
Что входит в работу
- Аудит текущей схемы и истории версий
- Написание Migration объектов для всех изменений
- Тесты через MigrationTestHelper для каждой миграции
- Настройка экспорта JSON-схем
- Обработка edge cases: пустые таблицы, внешние ключи, индексы, триггеры
Сроки
1–2 простые миграции (добавление колонок): 0,5–1 день. Сложная реструктуризация с полным покрытием тестами: 2–3 дня. Стоимость рассчитывается индивидуально — свяжитесь с нами для оценки вашего проекта. Получите консультацию по миграции Room уже сегодня. Закажите детальный аудит базы данных.







