Android Roomのmigrationをやってみた
1 やりたいこと
タイトル通り、Androidでローカルデータベースにデータを保存するRoomのmigrationを色々試したものを共有します。
Androidの中でデータベースを使おうとすると、SQLiteを使う方法[1]とRoomを使う方法[2]がある。後者はdata classを作って、少しコードを書くだけで使いやすい。そこで、Roomを使用したいと思う。ところが、アプリを保守したり開発を進めたりすると、データベースが変わることがあり、その際にmigrationが必要である。その、migration方法についてまとめる。
すぐに本題に入りたい方は4章までジャンプしてください。
2 Roomの導入
Roomを使うにはapp/build.gradleを下記のように書き換える。
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
...中略
dependencies {
...中略
//Roomを使うために下記を追加
implementation "androidx.room:room-runtime:2.4.0"
implementation "androidx.room:room-ktx:2.4.0"
kapt "androidx.room:room-compiler:2.4.0"
}
3 初期データベースの作成と利用
3.1 エンティティの作成
はじめにエンティティを作成します。このコードはAndroid公式のページ[2:1]を引用して、tableNameの指定だけ追加しています。
package email.whoto.dbmigrationtest
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "dbTest")
data class DBTest(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
エンティティとテーブルの違い
Roomの使用方法のページ[2:2]に「エンティティ」という言葉が出てきて、エンティティとテーブルの違いについて言葉で説明できないと思い、調べた内容を書きます。TeamLAB[3]によると
エンティティ : データベースで管理する実体。言い換えると管理したい情報のこと。多くは管理したい情報の名前。
例)
エンティティ:メール
属性:送信日時、宛先、送り主、本文
テーブル : エンティティを元に設計された概念モデルを、RDB(Relational Data Base)で扱える形にしたもの。
例)
UID | 送信日時 | 宛先 | 送り主 | 本文 |
---|---|---|---|---|
1 | 2021-01-01 | you@aaa.bbb | me@aaa.bbb | あけましておめでとうございます。 |
上の例だと有り難みがないですが、エンティティ同士の関係が1対多のとき、テーブルが複数にまたがることがあるので、エンティティとテーブルの関係は必ずしも双射(それぞれの属性が対の関係になること)ではないです。
3.2 Dao(Data Access Object)の作成
データベースにアクセスする際に使用するメソッドを格納したものを、Daoと言います。それを作っていきます。今回は、完全にAndroid公式ページ[2:3]から引用します(後で書き換えていきます)。
package email.whoto.dbmigrationtest
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
@Dao
interface DBDao {
@Query("SELECT * FROM dbTest")
fun getAll(): List<DBTest>
@Query("SELECT * FROM dbTest WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<DBTest>
@Query(
"SELECT * FROM dbTest WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1"
)
fun findByName(first: String, last: String): DBTest
@Insert
fun insertAll(vararg users: DBTest)
@Delete
fun delete(user: DBTest)
}
3.3 Databaseの作成
こちらも、Android公式ページ[2:4]から引用します。
package email.whoto.dbmigrationtest
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = arrayOf(DBTest::class), version = 1, exportSchema = false)
abstract class DBDatabase : RoomDatabase() {
abstract fun userDao(): DBDao
}
3.4 作成したデータベースのインスタンスを作成し、アクセスする
こちらも、基本的にはAndroid公式ページ[2:5]を参考にします。
class TestDBImpl @Inject constructor(): TestDB {
override suspend fun initialInsert(context: Context): Boolean =
withContext(Dispatchers.Default) {
val db = Room.databaseBuilder(
context,
DBDatabase::class.java, "database-name"
).build()
val testDB = db.userDao()
testDB.insertAll(
DBTest(
uid = 1,
firstName = "Taro",
lastName = "Usami",
),
DBTest(
uid = 2,
firstName = "Hanako",
lastName = "Saito"
)
)
val result = testDB.getAll()
for(r in result) {
Log.d("DB Test", "${r.lastName}, ${r.firstName}")
}
true
}
}
実行結果は以下です
~dbmigrationtest D/DB Test: Usami, Taro
~dbmigrationtest D/DB Test: Saito, Hanako
無事、2つの行が挿入できていそうなことがわかりました。
databaseBuilderの中の"database-name"は何か
気になったので調べてみました。stackoverflow[4]なので正しい情報か分かりませんが、データベースを作成するときに生成されるdbファイルの名前だそうです。理論上、一つのアプリで複数のデータベースにアクセスできるそうです。
RoomDatabaseオブジェクトをインスタンス化するときに気をつけるべきこと
4 データベースのエンティティを修正し移行(migration)する
ここから本題です。
4.1 新しいエンティティを作成する
先程作ったエンティティに、OSの種類という列を追加したいと思います。新しいエンティティは以下のようになります。
@Entity(tableName = "dbTest")
data class DBTest(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?,
@ColumnInfo(name = "os_name") val osName: String?,
)
4.2 移行するためのSQL文を書く
そして、移行するためのコードを書きます。SQL文になります。
val MIGRATION_1_2 = object : Migration(1,2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE dbTest ADD COLUMN os_name TEXT")
}
}
4.3 Daoのバージョンを上げる
忘れがちなのですが、バージョンを上げないとエラーが出てしまいます。
package email.whoto.dbmigrationtest
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = arrayOf(DBTest::class), version = 2, exportSchema = false)
abstract class DBDatabase : RoomDatabase() {
abstract fun userDao(): DBDao
}
4.4 移行用のSQL文をインスタンス作成時に呼ぶ
そして、この(4.2の)SQL文をaddMigrations()でDBDatabaseのインスタンスを作成するときに呼べば良いです。したがって、コードは以下のようになります。
package email.whoto.dbmigrationtest
import android.content.Context
import android.util.Log
import androidx.room.Room
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
class TestDBImpl @Inject constructor() : TestDB {
override suspend fun initialInsert(context: Context): Boolean =
withContext(Dispatchers.Default) {
val db = Room.databaseBuilder(
context,
DBDatabase::class.java, "database-name"
).addMigrations(MIGRATION_1_2)
.build()
val testDB = db.userDao()
testDB.insertAll(
DBTest(
uid = 3,
firstName = "Haru",
lastName = "Usami",
osName = "Ubuntu",
),
DBTest(
uid = 4,
firstName = "Hikari",
lastName = "Saito",
osName = "macOS",
)
)
val result = testDB.getAll()
for (r in result) {
Log.d("DB Test", "${r.lastName}, ${r.firstName}, ${r.osName}")
}
true
}
}
4.5 実行結果
このコードを実行すると以下のような結果が得られ、移行が完了しました。
~dbmigrationtest D/DB Test: Usami, Taro, null
~dbmigrationtest D/DB Test: Saito, Hanako, null
~dbmigrationtest D/DB Test: Usami, Haru, Ubuntu
~dbmigrationtest D/DB Test: Saito, Hikari, macOS
5. 実は…
本当は、Migrationにテストを導入して、それを中心に書きたかったのですが、エラーを倒すことが出来ず、力尽きた形になりました(無念…)。
また、機会を見つけて追加したいと思います。
どなたか、RoomのMigrationのテストについて、わかる方がいらっしゃいましたら、ぜひzennに投稿をお願い致します。
-
AndroidでSQLiteを使う方法 https://developer.android.com/training/data-storage/sqlite?hl=ja (2021-12-22閲覧) ↩︎
-
AndroidでRoomを使う方法 https://developer.android.com/training/data-storage/room?hl=ja (2021-12-22閲覧) ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
-
データベースを設計してみよう/TeamLAB https://team-lab.github.io/skillup/step2/09-db-design.html (2021-12-22閲覧) ↩︎
-
What is the database name in the Room.databaseBuilder? https://stackoverflow.com/questions/64560980/what-is-the-database-name-in-the-room-databasebuilder (2021-12-22閲覧) ↩︎
-
トッププログラミングオブジェクト指向2020.9.22更新 シングルトン 【singleton】 https://e-words.jp/w/%E3%82%B7%E3%83%B3%E3%82%B0%E3%83%AB%E3%83%88%E3%83%B3.html (2021-12-22閲覧) ↩︎
-
Room Library Reference https://developer.android.google.cn/jetpack/androidx/releases/room?hl=ja (2021-12-22閲覧) ↩︎
Discussion