SysTecevo

Android Room Migrationとは?既存データを消さずにDBを更新する方法・エラー・テストまで解説

初回掲載日:
最終更新日:

Android Room Databaseを旧versionから新versionへMigrationするオリジナル図解

AndroidアプリのEntityやテーブルを変更したとき、既存ユーザーの端末には以前のRoom Databaseが残っています。新しいアプリをインストールした端末だけを見ていると気づきにくいのですが、公開済みアプリの更新では、古いDBを新しいschemaへ変換するMigrationが必要になることがあります。

先に結論:既存データを残したいなら、DB versionを上げるだけでは不十分です。旧versionから新versionへ到達するMigrationを定義し、databaseBuilderへ登録し、過去versionからのテストとデータ保持確認を行ってから公開します。fallbackToDestructiveMigration()は、Migrationエラーを隠す設定ではなく、DBを破棄して再作成する可能性がある設定です。

Room Migrationとは?

Room Migrationは、アプリ内のSQLiteデータベースを旧schemaから新schemaへ移行する仕組みです。たとえばversion 1のUserテーブルへageカラムを追加してversion 2にするなら、既存端末で次の変換を実行します。

旧DB
version 1
→
Migration
1 → 2
→
新DB
version 2

新規インストールでは最新版schemaのDBを最初から作成できます。一方、既存ユーザーはさまざまな旧versionから更新するため、サポート対象の開始versionから最新版までの経路を用意しなければなりません。

DB versionとアプリのversionCodeは別物

@Database(version = 3)のversionはRoomが管理するDB schemaのversionです。Google Playへ配信するアプリのversionCodeとは目的が異なります。

番号何を管理するか主な用途
Room DB versionローカルDBのschemaMigrationの開始・終了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からv3へ移行するRoom Migration pathの図
新規インストールはv3を作成し、既存v1・v2は対応するMigration pathを通ってv3へ到達します。

最新版が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の違い

項目AutoMigrationManual Migration
向いている変更schemaから推論できる単純な変更データ変換、複雑なSQL、曖昧な変更
必要な情報過去schemaのexportMigration内の処理とSQL
rename / deleteAutoMigrationSpecなどで意図を明示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をテストする流れを案内しています。

  1. Migration対象の旧version DBを作成する
  2. 旧schemaに合うSQLでテストデータを投入する
  3. runMigrationsAndValidateでMigrationを実行する
  4. 新schemaの検証に成功したことを確認する
  5. 名前、件数、設定値など既存データを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は使ってよい?

Room Migrationでデータ保持を確認し、破壊的Migrationは再生成可能なデータだけに限定する判断フロー
既存データを残すアプリではMigrationを作ってテストします。破壊的Migrationはデータが消える可能性があるため限定的に扱います。

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とデータ保持テストを公開前の必須工程にしてください。

参考にした公式情報