
みなさまこんにちは〜!
メモリアインクのすだです。
本日は、Roomで内部データベースを実装した際にマイグレーション処理を行う方法について、わかりやすく解説していきます!
この記事を読んでわかること…
・Roomのマイグレーションとは
・Roomのマイグレーション例
・Roomのマイグレーション時の注意点
環境
- Kotlin (ver 1.9.0)
- Android Studio (Giraffe | 2022.3.1 Patch 3)
Roomのマイグレーションとは?
マイグレーション(migration)とは、
Roomで一度作ったデータベースの構造(テーブルやカラム)を変更する際に必要な「バージョン移行処理」のことです。
Roomは、構造が変わったまま実行されると「このデータベースは前と違う」と判断し、アプリをクラッシュさせる仕様になっています。
それを避けるために、「どうやって変えたか」を明示的に伝える必要があるのです。
Roomでマイグレーションが必要になる場面
以下のような場合には、マイグレーション対応が必須です:
- カラム(列)を追加・削除した
- テーブルを追加・削除した
- データ型や初期値を変えた
- 主キーやインデックスを変更した
Roomはデータベースの整合性を守るため、
バージョンが変わったのにマイグレーション処理が指定されていないとクラッシュします。
java.lang.IllegalStateException:
A migration from version X to Y was required but not found.
というようなエラーが発生し、アプリが起動しなくなってしまいますので、注意してください。
Roomのマイグレーション方法
基本的には、addMigrations()
で手動マイグレーションを定義する形となります。
旧バージョンのEntityを以下として、今回はカラムを新しく追加するというマイグレーションを実行していきます。
旧バージョン▼
@Entity
data class User(
@PrimaryKey val id: Int,
val name: String
)
Step1. カラムを追加
@Entity
data class User(
@PrimaryKey val id: Int,
val name: String,
val age: Int // ← 追加されたカラム
)
Step2. Database定義のversionを更新
@Database(entities = [User::class], version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
ここで必ず、versionを+1してください。
Step3. マイグレーション処理を追加&DB初期化処理も修正
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// 新しいカラム「age」を追加
database.execSQL("ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
}
}
val MIGRATION_1_2 = object : Migration(1, 2) {
ここで、マイグレーション処理をオブジェクトとして定義します。Migration
クラスを継承し、「バージョン1 → バージョン2」への移行処理をこの中に記述します。
database.execSQL(...)
の部分では、SQL文を直接実行します。
今回の例では、ALTER TABLE
文を使って、User
テーブルに age
カラムを追加しています。
(重要:SQLiteではカラム追加時に 必ず初期値(DEFAULT)とNOT NULL制約をセットで指定する必要があります。でないと、既存のデータが入らないためエラーになります。)
val db = Room.databaseBuilder(
context,
AppDatabase::class.java,
"user-db"
).addMigrations(MIGRATION_1_2)
.build()
そして、ビルダーに.addMigrations(MIGRATION_1_2)
を追加して定義したマイグレーション処理を適用します。
これにより、既存データを維持したまま新しいカラムが使えるようになります。
Roomは、データベースの現在のバージョンから最新版までのマイグレーション手順をすべて連続して適用する仕組みです。
そのため、過去のマイグレーションの履歴を全て残しておく必要があります。
例)バージョン1 → 4 にアップグレードされた場合
// v1 → v2: ageカラムを追加
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
}
}
// v2 → v3: createdAtカラムを追加
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE User ADD COLUMN createdAt TEXT DEFAULT '' NOT NULL")
}
}
// v3 → v4: Addressテーブルを新規作成
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("""
CREATE TABLE IF NOT EXISTS Address (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
userId INTEGER NOT NULL,
address TEXT NOT NULL,
FOREIGN KEY(userId) REFERENCES User(id)
)
""".trimIndent())
}
}
初期化時に全ての履歴を指定してあげてください。
val db = Room.databaseBuilder(
context,
AppDatabase::class.java,
"user-db"
)
.addMigrations(
MIGRATION_1_2,
MIGRATION_2_3,
MIGRATION_3_4
)
.build()
fallbackToDestructiveMigrationを使う場合
fallbackToDestructiveMigrationはマイグレーション処理をスキップして、データベースを再作成してくれるメソッドです。
保存していたデータは消えるので、本番環境にはおすすめしませんが、主に開発・デバッグ中の一時的な手段としては有効です。
val db = Room.databaseBuilder(
context,
AppDatabase::class.java,
"user-db"
).fallbackToDestructiveMigration()
.build()
まとめ
おつかれさまでした。
いかがでしたでしょうか!
Roomを使用している上で、DB構造が変わったときには、
必ずこのマイグレーション処理を実装してください!



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント