Roomのマイグレーションまとめ

Kenji Abe
8 min readOct 21, 2019

--

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管理しておきましょう。

1.json

もし、追加し忘れて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のほうになるので間違えないように注意してください。

参考

--

--

Kenji Abe
Kenji Abe

Written by Kenji Abe

Programmer / Gamer / Google Developers Expert for Android, Kotlin / @STAR_ZERO

No responses yet