Android Room Migrationとは?既存データを消さずにDBを更新する方法・エラー・テストまで解説
初回掲載日:
最終更新日:
AndroidアプリのEntityやテーブルを変更したとき、既存ユーザーの端末には以前のRoom Databaseが残っています。新しいアプリをインストールした端末だけを見ていると気づきにくいのですが、公開済みアプリの更新では、古いDBを新しいschemaへ変換するMigrationが必要になることがあります。
fallbackToDestructiveMigration()は、Migrationエラーを隠す設定ではなく、DBを破棄して再作成する可能性がある設定です。Room Migrationとは?
Room Migrationは、アプリ内のSQLiteデータベースを旧schemaから新schemaへ移行する仕組みです。たとえばversion 1のUserテーブルへageカラムを追加してversion 2にするなら、既存端末で次の変換を実行します。
version 1
1 → 2
version 2
新規インストールでは最新版schemaのDBを最初から作成できます。一方、既存ユーザーはさまざまな旧versionから更新するため、サポート対象の開始versionから最新版までの経路を用意しなければなりません。
DB versionとアプリのversionCodeは別物
@Database(version = 3)のversionはRoomが管理するDB schemaのversionです。Google Playへ配信するアプリのversionCodeとは目的が異なります。
| 番号 | 何を管理するか | 主な用途 |
|---|---|---|
| Room DB version | ローカルDBのschema | Migrationの開始・終了version |
| アプリ versionCode | アプリ配信版 | Google Playの更新順序 |
| versionName | ユーザー向け表示名 | 1.0.0などのリリース表記 |
この2つは連動することはありますが、同じ数値にする必要はありません。アプリのversionCodeについては、versionCodeとversionNameの違いを整理した記事も参照してください。
Room 3.0とRoom 2.xのコードを混ぜない
この記事のコード例は、公開時点のAndroid Developers公式資料に合わせてRoom 3.0系のKotlin API(androidx.room3)を前提にしています。Room 3.0はKotlin-firstで、KSPや新しいSQLite driverを使う構成です。
既存アプリがRoom 2.xの場合は、androidx.room、SupportSQLiteDatabaseなど従来APIの構成が残っていることがあります。Room 3.0用コードをそのまま貼り付けず、使用中のRoom系列と公式の移行ガイドを確認してください。Room 2.xから3.0へ移す場合は、まず現在のRoom 2.xを更新してからRoom 3へ切り替える段階的な移行が公式に案内されています。
変更内容ごとの「Migrationは必要?」判断
| schema変更 | 判断の目安 | 注意点 |
|---|---|---|
| テーブル追加 | Migrationが必要 | 新テーブルの定義・index・初期値を確認 |
| nullableなカラム追加 | AutoMigrationで扱いやすい場合がある | Entityと実DBのnullableが一致するか確認 |
| NOT NULLカラム追加 | default値などを含めて設計 | 既存行を埋められる値が必要 |
| カラム名・テーブル名変更 | 曖昧な変更なので要確認 | AutoMigrationSpecまたはManual Migrationを検討 |
| カラム削除・テーブル削除 | データ消失を伴うため要確認 | 本当に削除してよいか、復元可能かを確認 |
| 型変更・データ分割 | Manual Migrationが必要になりやすい | 値の変換、移動、欠損時の扱いを定義 |
| index・constraint変更 | 生成後schemaを検証 | index名、unique、primary keyを確認 |
Entityのプロパティを変更しただけでも、実際のSQLite schemaが変わるならMigration対象です。コード上の変更と実DBの変更を分けて考えるのがポイントです。
Manual Migration:1→2でカラムを追加する
Room 3.0の公式例に合わせ、User(id, name)へageを追加するケースを考えます。既存行には0を入れ、NOT NULL制約を満たすSQLにします。
val MIGRATION_1_2 = object : Migration(1, 2) {
override suspend fun migrate(connection: SQLiteConnection) {
connection.executeSQL(
"ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
)
}
}
Migrationを定義しただけでは使われません。DBを構築する場所で登録します。
val db = Room.databaseBuilder<AppDatabase>(
context = context,
name = "app.db"
)
.addMigrations(MIGRATION_1_2)
.build()
実際の型名やbuilderの書き方は、使用するRoom 3.0の依存関係・driverに合わせて公式APIを確認してください。Room 2.xではMigrationの引数やSQL実行オブジェクトが異なるため、同じコードを混用しないでください。
1→2→3のMigration pathをつなぐ

最新版がversion 3なら、典型的には次のように考えます。
- 新規インストール:最初からversion 3のDBを作成
- 既存version 1:1→2、2→3を通ってversion 3へ
- 既存version 2:2→3を通ってversion 3へ
Roomは登録されたMigrationを組み合わせて経路を探します。直接1→3のMigrationを用意する設計もできますが、サポートする旧versionから最新版へ実際に到達できるかを確認することが重要です。1→2だけをテストしても、1→3や2→3の利用者を安全に更新できるとは限りません。
AutoMigrationとManual Migrationの違い
| 項目 | AutoMigration | Manual Migration |
|---|---|---|
| 向いている変更 | schemaから推論できる単純な変更 | データ変換、複雑なSQL、曖昧な変更 |
| 必要な情報 | 過去schemaのexport | Migration内の処理とSQL |
| rename / delete | AutoMigrationSpecなどで意図を明示 | SQLで移行手順を明示 |
| メリット | 定型コードを減らせる | 変換ルールを細かく制御できる |
| 注意点 | 何でも自動ではない | schema検証とデータテストが必要 |
テーブルやカラムのrename・deleteのように、単なる追加なのか名前変更なのかをschemaだけで確定できない変更では、AutoMigrationSpecと対応するannotationで意図を示す必要があります。1つのversion間にManualとAutoの両方を定義した場合は、公式説明ではManual Migrationが優先されます。
exportSchemaとschema JSONをGitで管理する
AutoMigrationの生成や過去schemaの確認には、Roomが出力するschema JSONが役立ちます。Room 3.0ではRoom Gradle PluginのschemaDirectoryで保存先を設定する構成が公式に案内されています。Room 2.xではexportSchemaなど系列に応じた設定を使います。
plugins {
id("androidx.room3")
}
room3 {
schemaDirectory("$projectDir/schemas")
}
schema JSONはユーザーの実DBやユーザーデータのバックアップではありません。あくまで各DB versionの構造履歴です。Migrationテストとレビューに必要なので、秘密情報を含めない形でversion controlへ保存します。
MigrationTestHelperで旧DBから検証する
Migrationテストでは、単にアプリが起動したかだけでなく、旧versionのDBを作り、テストデータを入れ、Migrationを実行し、schema validationとデータ保持を確認します。Room公式は、最初のversionから最新版までのMigrationをテストする流れを案内しています。
- Migration対象の旧version DBを作成する
- 旧schemaに合うSQLでテストデータを投入する
runMigrationsAndValidateでMigrationを実行する- 新schemaの検証に成功したことを確認する
- 名前、件数、設定値など既存データをassertする
// Room 3.0系の概念例。テスト用driverとschema設定は公式APIに合わせる
val oldDb = helper.createDatabase(1)
oldDb.execSQL("INSERT INTO User (id, name) VALUES (1, 'test')")
oldDb.close()
val migratedDb = helper.runMigrationsAndValidate(
2,
listOf(MIGRATION_1_2)
)
// migratedDbから、id=1のデータと追加カラムを検証する
Room 2.xではandroidx.room:room-testing、Room 3.0ではroom3-testingなど、依存関係とhelperの構成が異なります。テストの考え方は同じでも、APIは使用系列に合わせてください。DAOは最新版schemaを前提にするため、旧version DBへの初期データ投入は公式例のようにSQLで行うのが安全です。
Migrationエラーの原因と確認順
| 症状・エラー | 原因候補 | 確認・対処 |
|---|---|---|
| Migration pathが見つからない | 開始versionから終了versionへのMigration未登録 | start/end version、addMigrations、全経路を確認 |
| Migration didn't properly handle... | Entityが期待するschemaと実DBが不一致 | expected / found、nullable、default、型、index、主キーを比較 |
| NOT NULLカラム追加で失敗 | 既存行を埋める値がない | nullableまたは適切なdefault値を設計 |
| rename後にデータが消える | renameではなく削除+追加として扱われた | AutoMigrationSpecまたはManual Migrationで意図を明示 |
| 最新版では動くが更新端末でクラッシュ | 旧versionからのpathをテストしていない | サポート対象の最古versionから最新版まで検証 |
Roomが必要なMigration pathを見つけられない場合、公式APIではIllegalStateExceptionになるケースが説明されています。エラーメッセージだけを隠すのではなく、開始versionと終了version、登録漏れ、schema JSONを順番に確認します。
fallbackToDestructiveMigrationは使ってよい?

fallbackToDestructiveMigrationは、必要なMigration pathが見つからないときにDBを破棄して再作成するfallbackです。メモ、履歴、設定、在庫などユーザーが復元できないデータを保存しているアプリで、エラー回避のために安易に有効化してはいけません。
| DBの性質 | 判断の目安 |
|---|---|
| 完全なキャッシュ | サーバーから再取得できるなら候補。ただし再生成後の挙動をテスト |
| テスト専用DB | テストの目的に応じて許容できる場合がある |
| ユーザー入力・履歴・ローカル専用データ | 原則としてMigrationで保持。破壊的fallbackを避ける |
特定の旧versionからだけdestructive fallbackを許可するfallbackToDestructiveMigrationFromや、downgrade時を対象にするAPIもあります。これらも「データを消してよいversion」を明示する設定であり、データ保持の代わりにはなりません。テスト環境と本番環境で意図が同じか確認してください。
リリース前チェックリスト
- DB schemaを変更した理由と影響範囲を確認した
@DatabaseのDB versionを正しく上げた- 旧versionから最新版へのMigration pathを登録した
- AutoMigrationで曖昧なrename / deleteを放置していない
addMigrationsをdatabaseBuilderへ追加した- schema JSONを生成し、Gitで差分をレビューした
- サポート対象の古いversionからMigrationテストを実行した
- 既存データの件数・代表値・null扱いを検証した
- release buildの実際のDB初回openを確認した
- destructive migrationを意図せず有効にしていない
- Migrationとバックアップ / restoreを混同していない
よくある質問
Entityにカラムを追加しただけでもMigrationは必要ですか?
既存端末の実DB schemaが変わるなら必要になることがあります。nullable、default値、AutoMigrationで扱えるかを確認し、最終的にはMigrationテストで検証します。
DB versionを上げるだけではダメですか?
ダメな場合があります。versionを上げるとRoomは新schemaを期待しますが、旧DBをどう変換するかは別途Migrationとして登録する必要があります。
1→2と2→3があれば、1→3へ更新できますか?
登録された経路とRoomの設定が条件を満たせば到達できますが、サポート対象の旧versionから最新版まで実際にテストしてください。直接1→3を用意する設計では、どの経路が選ばれるかも確認します。
Migrationでデータを消したくない場合は?
destructive fallbackに頼らず、旧schemaに合わせたテストデータを用意してMigration後の値を検証します。複雑なデータ変換はManual Migrationで明示します。
schema JSONにユーザーデータは入りますか?
schema JSONはDB構造の履歴であり、ユーザーの実データそのものではありません。バックアップの代わりではないので、別途バックアップ方針を考えます。
Room 2.xとRoom 3.0はどちらを使えばよいですか?
既存アプリは現在の依存関係を確認し、移行を急に混ぜないことが重要です。新規構成・Room 3への移行は公式のRoom 2.x→3.0ガイドに従い、記事のRoom 3.0コードをRoom 2.xへそのまま貼り付けないでください。
まとめ
Room Migrationの本質は、Entityを変更するコードを書くことではなく、旧DBを新schemaへ安全に変換し、既存データが保持されることを確認することです。まずDB versionを整理し、AutoMigrationかManual Migrationかを選び、必要なMigrationをbuilderへ登録します。そのうえでschema JSONを保存し、MigrationTestHelperなどで古いversionから最新版までテストします。
fallbackToDestructiveMigrationは便利な近道ではなく、DBを破棄する可能性がある最後の選択肢です。ユーザーが失うと困るデータを持つアプリでは、Migration pathとデータ保持テストを公開前の必須工程にしてください。