RoomはAndroidでSQLiteを扱うためのライブラリで、Jetpackの一部です。生のSQLiteの上に抽象化レイヤーを提供し、テーブル作成、クエリ実行、KotlinおよびJavaオブジェクトへのデータ変換を自動化します。Android Developersによると、Roomはビルド時にSQLクエリをコンパイルし、構文の正確性とEntityとテーブル間の関係を検証します。
重要なポイント
RoomはAndroid Jetpackの永続化ライブラリで、SQLiteのオブジェクト関係マッピングを提供します。Roomは生のSQLiteの3つの主要な問題を解決します:テーブル作成のための大量のボイラープレートコード、コンパイル時のSQLクエリ検証の欠如、およびCursorからオブジェクトへの手動変換です。
このライブラリはアノテーションプロセッサ(kaptまたはKSP)を使用し、ビルド時に抽象的なRoomDatabaseおよびDAOクラスの実装を生成します。これにより、SQLの構文エラーや型の不一致が、アプリケーションの実行前、つまりGoogle Playに公開した後のランタイムではなく、開発中に発見されることが保証されます。
Google I/O 2023によると、Roomはローカルデータを扱うAndroidアプリケーションの68%で使用されています。これは、時代遅れのSQLiteOpenHelperやContentProviderに代わり、Googleがすべての新規プロジェクトに推奨するデバイス上のデータストレージの標準です。
サーバーからのローカルデータキャッシング、オフラインモード、または複雑なSQLクエリを実行できる構造化ユーザーデータの保存が必要なプロジェクトにRoomを導入してください。
RoomはAndroid Jetpackの一部であり、ローカルデータを扱うすべての新規プロジェクトにGoogleが公式に推奨しています。RealmやObjectBoxとは異なり、RoomはネイティブのSQLiteを使用するため、DB BrowserからDataGripまで、あらゆるサードパーティのデータベースツールとの互換性が保証されます。開発者はアプリケーションの.dbファイルを開いて直接SQLクエリを実行できるため、開発中のデバッグとデータ分析が簡素化されます。
Entityは@Entityでアノテーションされたデータクラスで、Roomがデータベーステーブルに変換します。クラスの各フィールドはテーブルのカラムになり、各インスタンスは行になります。Roomはリフレクションを使用してフィールドにアクセスするため、必須の識別子には@PrimaryKeyアノテーションが必要です。
@Entityアノテーションは、クラスがテーブルであることをRoomに通知します。tableNameパラメータは、クラス名と異なる場合にテーブル名を設定します。@PrimaryKeyは、autoGenerate = trueによる自動生成をサポートして主キーを定義します。
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
@Ignore
val tempData: String?
)
@ColumnInfoは、Kotlinのフィールド名と異なる場合にテーブルのカラム名を指定します。@Ignoreはフィールドをテーブルから除外します — データベースに保存されません。@ForeignKeyは、削除または更新時のカスケード操作を含むテーブル間の関係の外部キーを記述します。
Roomは@Embeddedアノテーションによるネストされたオブジェクトをサポートします。ネストされたクラスのフィールドは、名前の競合を避けるためにプレフィックスを付けて親テーブルのカラムに展開されます。たとえば、Userに埋め込まれたcityおよびstreetフィールドを持つAddressクラスは、usersテーブルにaddress_cityおよびaddress_streetカラムを作成し、単純な値オブジェクトに個別のテーブルを必要としません。
Roomはプリミティブ型とそのラッパーのみをサポートします。リスト、Date、カスタム型を保存するには、@TypeConverterを使用します — カスタム型とSQLiteプリミティブ間の変換用の静的メソッドです(例:ListとJSON文字列間)。
DAO(データアクセスオブジェクト)は、@Daoでアノテーションされたインターフェースまたは抽象クラスで、データアクセスのためのメソッドを含みます。各メソッドはSQL操作でアノテーションされます:@Insert、@Update、@Delete、または明示的なSQLクエリを持つ@Query。
@QueryアノテーションはSQL文字列を受け取り、Roomがコンパイル時に構文の正確性とEntityフィールドとのカラム名の一致を検証します。Roomは:paramName構文によるパラメータ化クエリをサポートします。
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM users ORDER BY name ASC")
fun getAllUsers(): Flow<List<User>>
}
@Insertは、重複レコード挿入時の競合を処理するためのOnConflictStrategyオプションをサポートします。戻り値の型としてのFlowは、テーブルのデータ変更ごとにリアクティブなUI更新を提供します — サブスクリプションはINSERT、UPDATE、DELETEのたびに自動的に再開されます。
@Transactionアノテーションは、単一のトランザクションブロック内での複数の操作のアトミック実行を保証します。Roomは実行中にデータベースをロックし、複数のスレッドからの同時アクセス時のレースコンディションを防ぎます。
RoomDatabaseは、EntityとDAOを単一のデータベースアクセスポイントに結合する抽象クラスです。スキーマバージョンとEntityクラスのリストを指定してRoom.databaseBuilderを介して作成されます。データベースインスタンスは、複数の接続を避けるためにlazyデリゲートを使用してシングルトンとして作成する必要があります。
Roomのマイグレーションは、古いスキーマバージョンから新しいバージョンへの移行のためのSQLスクリプトを記述するMigrationクラスです。スキーマ変更時にマイグレーションが提供されない場合、RoomはIllegalStateExceptionをスローします。これにより、アプリケーション更新時の偶発的なユーザーデータ損失から保護されます。
val migration_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
val db = Room.databaseBuilder(
getApplication(),
AppDatabase::class.java,
"app_database"
).addMigrations(migration_1_2)
.build()
開発時には、fallbackToDestructiveMigrationを使用できます。これはバージョン不一致時に古いデータベースを削除して新しいものを作成します。このモードはデバッグ専用です — プロダクションリリースには適切なマイグレーションを含める必要があります。
データベーステストのために、Roomは特別なクラスRoom.inMemoryTestBuilderを提供します。これはディスクに保存せずにインメモリデータベースを作成します。各テスト完了後、データベースは自動的に破棄され、テストシナリオの完全な分離が保証されます。android-arch-core-testingライブラリと組み合わせることで、開発者は手動で状態をクリーンアップすることなくデータベースのライフサイクルを管理し、マイグレーションの正確性を検証できます。
Roomのパフォーマンスはクエリ構造とインデックスに直接依存します。遅いクエリを分析するために、RoomはenableQueryCallbackフラグを提供し、実行時間とともにすべてのSQLクエリをログに記録します。開発者はこのログを使用して100ミリ秒以上かかるクエリを見つけ、@Entityの@Indexアノテーションで複合インデックスを追加したり、@Relationを使用してサブクエリを直接JOIN操作に書き換えたりして最適化できます。
RoomはSQLCipherによるデータベース暗号化もサポートしています。net.zetetic:android-database-sqlcipherライブラリを追加し、標準の代わりにSupportFactoryを使用すると、DAOクエリやEntity構造を変更せずにディスク上のすべてのデータの透過的な暗号化が提供されます。これはユーザーの個人データを扱うアプリケーションに必要であり、GDPRおよび個人データ保護に関するロシア連邦法152-FZに準拠しています。暗号化パスワードは、root化されたデバイスでのツールによる抽出から保護するためにAndroid Keystoreに保存できます。
Roomはバージョン2.1以降、Kotlin Coroutinesをネイティブでサポートしています。DAOメソッドはsuspend関数にでき、メインスレッドをブロックせずにバックグラウンドスレッドでクエリを実行します。Roomはディスパッチャを自動的に管理し、読み取りおよび書き込みクエリにDispatchers.IOを使用します。
リアクティブクエリのために、RoomはFlowを返します — 影響を受けるテーブルの変更ごとに新しい値を発行するコールドデータストリームです。ViewModelはstateInまたはcollectを介してFlowにサブスクライブし、アダプタに手動で通知することなく自動的にUIを更新します。
Roomは、SQLiteからページごとにデータをロードする特別なPagingSource実装を通じてPaging 3もサポートしています。これは数千のレコードを持つ大きなリストに効率的です:Paging 3は画面に表示されている行のみをロードし、データベースの変更時に自動的に更新します。
オフラインアクセスと無限スクロールを備えたニュースフィード、操作ログ、製品リストを表示するには、Roomと共にPaging 3を使用してください。
よくある質問
Roomはテーブル作成、Cursorからオブジェクトへの変換、コンパイル時のSQL検証を自動化します。SQLiteOpenHelperでは手動でスキーマを記述し、Cursorを処理する必要があり、アプリケーション実行前のクエリ検証がないため、エラーのリスクが高まります。
はい、Entityを変更する場合(フィールドの追加/削除、型の変更)、マイグレーションが必要です。これがないと、Roomは起動時にIllegalStateExceptionをスローします。開発時にはfallbackToDestructiveMigrationを有効にできますが、プロダクションリリースでは正しいマイグレーションスクリプトが必要です。
Roomはカスケード操作用の@ForeignKeyとネストされたオブジェクト用の@Relationをサポートしています。複雑なJOINクエリには、@Embeddedと@Relationを介してネストされたエンティティを持つPOJOを返す@Queryと共に@Transactionアノテーションを使用します。
はい、RoomはJavaと完全に互換性があります。suspend関数の代わりにLiveDataまたはRxJava Observableを、Flowの代わりにLiveDataを使用します。JavaでのRoomは同じすべてのアノテーションをサポートしますが、非同期操作により多くのボイラープレートコードが必要です。
RoomはZeteticのSQLCipherによる暗号化をサポートしています。Room.databaseBuilderの代わりに、net.zetetic:android-database-sqlcipherライブラリのSupportFactoryを使用し、暗号化パスワードを渡します。ディスク上のすべてのデータは、DAOクエリに対して透過的に暗号化されます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。