Room
Roomは、アプリケーションコードとSQLiteストレージの間で機能するラッパー(抽象化コンポーネント)である。SQLiteは、アプリケーションのあらゆるデータを端末内のファイルとして保持するデータベースと捉えられる。Roomは、このSQLite上のデータに対するCRUD操作を一貫して処理すると同時に、データ定義やデータの扱い方を記述できる抽象化レイヤーを提供する。
Roomの詳細に関しては以下参照。
https://developer.android.com/training/data-storage/room?hl=ja
Roomを構成する主要コンポーネント
エンティティ(Entity)
- 役割:テーブルの構造を定義し、テーブル行のデータを保持するクラス
-
主なアノテーション
-
@Entity:テーブルを定義する-
foreignKeys:外部キーの設定
-
-
@ColumnInfo:特定のカラムの設定を定義する -
@PrimaryKey:エンティティ内で一意性を保証する主キーを指定する -
@Embedded:テーブルの一貫性を保ちつつ、フィールドの多いオブジェクトの入れ子化を回避する
-
データアクセスオブジェクト(DAO)
- 役割:データに対して行う操作(CRUDなど)を規定するインターフェース/クラス
- 主なアノテーション
-
@Dao:DAO であることを宣言 -
@Insert:挿入操作を宣言 -
@Update:更新操作を宣言 -
@Delete:削除操作を宣言 -
@Query:クエリ操作を宣言
-
データベース(RoomDatabase)
-
@Database:データベースに格納するエンティティとバージョンを指定する - 各 DAO に対して、
RoomDatabase内で抽象メソッドを定義する。これによりビルドシステムは、これらのメソッドの実装を提供するサブクラスを生成する
Roomの注意点
- RoomはUIスレッド上での操作を一切許可しない
- Roomはコルーチン、RxJava、LiveDataなど複数のライブラリやフレームワークとの互換性を備える
Roomのマイグレーション
- Roomには標準でマイグレーション機能が備わっている
- 新しいコードをリリースした後、データベース構築コードが実行されると、保存データのバージョンとクラスで指定されたバージョンを比較して差異を検知する
- 最新バージョンに到達するまで指定されたマイグレーションを順次実行する
以下のコードはバージョン1からバージョン2にマイグレーションする例
/**
* Room データベース定義。
* - entities: この DB で管理するテーブル(User)
* - version : スキーマのバージョン。変更したら MIGRATION を用意して上げる
*/
@Database(entities = [User::class], version = 2)
abstract class UserDatabase : RoomDatabase() {
/** User テーブルに対する型安全な操作を提供する DAO の入口 */
abstract fun userDao(): UserDao
companion object {
/** アプリ全体で 1 つだけ使い回すインスタンス */
private lateinit var userDatabase: UserDatabase
/**
* v1 -> v2 への移行定義。
* users テーブルに role カラム(INT・NOT NULL・デフォルト0)を追加する。
*/
private val MIGRATION_1_2 = object: Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// 元コードの "users ADD COLUMN ..." は誤り。ALTER TABLE が必要。
database.execSQL(
"ALTER TABLE users ADD COLUMN role INTEGER NOT NULL DEFAULT 0"
)
}
}
/**
* アプリケーションコンテキストを用いて DB を生成/取得する。
* - Activity/Fragment の Context を直接渡さない(リーク防止)
* - 初回だけビルドして以降は同じインスタンスを返す
*/
fun getDatabase(applicationContext: Context): UserDatabase {
if (!::userDatabase.isInitialized) {
userDatabase = Room.databaseBuilder(
applicationContext,
UserDatabase::class.java,
"user-db" // 端末内に作られるファイル名
)
.addMigrations(MIGRATION_1_2) // 版上げ時の移行手順を登録
.build()
}
return userDatabase
}
}
}
TypeConverterインスタンス
@TypeConverterアノテーション
- Room がそのまま保存できない型を、SQLite が扱える基本型に変換/逆変換する関数へ付けるアノテーション
- @TypeConverterアノテーションを使用すると、Roomは変換が行われる関数を識別しやすくなる
以下、Java/KotlinとSQLiteの型対応表
| Java/Kotlin | SQLite |
|---|---|
| String | TEXT |
| Byte, Short, Integer, Long, Boolean | INTEGER |
| Double, Float | REAL |
| Array | BLOB |
以下実装例
/**
* Roomがそのままでは保存できない Date 型を
* Long(UNIXエポックミリ秒) と相互変換するコンバータ。
*
* 使い方:
* - エンティティ/DAO/Database に @TypeConverters(DateConverter::class) を付ける
* - または RoomDatabase.Builder#addTypeConverter(...) で登録
*/
class DateConverter {
/**
* Long(ミリ秒) -> Date に変換
*
* @param value DBから読み出したエポックミリ秒(null なら未設定)
* @return Date オブジェクト。value が null の場合は null を返す
*/
@TypeConverter
fun from(value: Long?): Date? {
// value が null ならそのまま null、値があれば Date に包んで返す
return value?.let { millis -> Date(millis) }
}
/**
* Date -> Long(ミリ秒) に変換(DBへ保存可能な型)
*
* @param date アプリ内で扱う Date(null なら未設定)
* @return 1970/01/01 00:00:00 UTC からの経過ミリ秒。date が null なら null
*/
@TypeConverter
fun to(date: Date?): Long? {
// date が null なら null、値があればミリ秒を返す
return date?.time
}
@Databse(entities = [User::class], version = 1)
// DateとLongの型変換を全体適用
@TypeConverts(DateConverter::class)
abstract class UserDatabase : RoomDatabase() {
// 省略
}
サンプルアプリケーション
Roomを使用したショートテキストの保存・読み込み・削除のサンプルアプリケーションは以下
https://github.com/motojimay/Room-test-android
