Roomのマイグレーションについてまとめておきます。
環境
- Room 2.2.0
やっておいたほうが良いこと
Roomのマイグレーションはテストが可能なのですが、その前にやっておいたほうが良い設定があります。
build.gradleに以下の設定を最初から追加しておくことをオススメします。
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
この設定がなくても、Room自体は使用可能なのですが、マイグレーションのテストするには必要になります。
この設定をしておくと、モジュール直下にschemasのディレクトリが作られ、 1.json
のようなバージョンごとのJSONファイルが生成されます。このJSONはGit管理しておきましょう。
もし、追加し忘れてJSONが作成されてない場合はGitから履歴を辿るとかをして、バージョンごとのJSONを改めて作成する必要があります。
基本的なマイグレーション
例として新しくテーブルを追加するとします。
新しくテーブルを追加するので @Entity
のdata classを追加する必要があります。更に @Database
にそのEntityを追加して、versionを2にします(schemasに新たに2.jsonが作成されます。)
@Database(
entities = [
...
],
version = 2
)
次にマイグレーションするためのSQLを書きます。今回はテーブルを追加するので、CREATE TABLE文を書きます。
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE ...")
}
}
Migration
を使って、引数にマイグレーションする開始のバージョンと終了バージョンを指定します。この例だと、バージョンが1から2に上がるときにこのSQLが実行されます。
最後にRoomの生成する際にさきほどマイグレーションのオブジェクトを渡してあげます。
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "sample")
.addMigrations(MIGRATION_1_2)
.build()
基本的なマイグレーションはこんな感じになります。
Migrationクラスについて
バージョンが一気に上がる場合
例えば、version=1はインストール済みだけど、version=2のアップデートをせずに、version=3のアップデートを行う場合ですね。
これはそこまで気にする必要はなく、1から2と2から3のマイグレーションをそれぞれ設定しておけば、自動で実行してくれます。
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// ...
}
}
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
// ...
}
}Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "sample")
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.build()
さらに追加でversion=1からversion=3へ一気に上げるときのマイグレーションを追加で書くことができます。
val MIGRATION_1_3 = object : Migration(1, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
// ...
}
}Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "sample")
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_1_3)
.build()
このように書いておくと、1から3へアップデートするときは、MIGRATION_1_3だけが実行されることになります。
例えば、1から2でCREATE TABLEして、2から3でカラム追加した場合は、1から3のマイグレーションは必要なのはカラムが追加されてるCREATE TABLEをすれば良いので、SQLを発行する回数が減ります。
このときの注意としては、MIGRATION_1_3があるからと言って、1ずつ増えるマイグレーションが不要なることなはないので、忘れずに設定しましょう。
変更ないけどversionを上げる場合
ほぼ起き得ない例だとは思いますが、DBには変更ないけどversionを上げる場合も addMigrations
が設定されていないと実行時にエラーになるので注意が必要です。
Migrations#migrate
が空の実装にして、設定すると良いと思います。
Databaseを再生成する
なんらかの原因でマイグレーションに失敗したりして、Databaseを再生成したい場合は fallbackToDestructiveMigration
でDatabaseを再生成することができます。
これを実行すると、すべてのデータが削除されるので注意してください。
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "sample")
.fallbackToDestructiveMigration()
.build()
また、あるバージョンにアップデートするタイミングでDatabaseを再生成するには、 fallbackToDestructiveMigrationFrom
を使ってバージョンを指定できます。先程の fallbackToDestructiveMigration
だとバージョンアップのたびに毎回Databaseが再生成されてしまうので、こちらのほうが推奨されています。
Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "sample")
.fallbackToDestructiveMigrationFrom(1, 2)
.addMigrations(MIGRATION_3_4)
.build()
この例だと、1から2と2から3にマイグレーションするときにDatabaseが再生成されますが、3から4のときは通常のマイグレーションが行われます。
引数のversionに何を指定するかが結構ややこしいので、しっかりテストするようにしたほうが良いでしょう。過去versionから一気にアップデートした場合など。
最後に、ダウングレードしたときにDatabaseを再生成するには fallbackToDestructiveMigrationOnDowngrade
があります。
マイグレーションのテスト
基本的に、公式ドキュメントを参照してもらえれば問題ないかと思います。
テストは、最初にやっておいたほうが良いことで書いた、SchemaのJSONを使うことになります。
localのunit testではなく、androidTestのほうになるので間違えないように注意してください。