RoomはAndroid JetpackのORMライブラリで、Android上のローカルデータベースを扱うためのSQLite上の抽象化レイヤーを提供します。Android Developers, 2025の公式ドキュメントによると、Roomはコンパイル時にアノテーションに基づいてDAO実装を自動生成し、SQLiteOpenHelperの直接使用と比較して約70%のボイラープレートコードを排除します。ライブラリはコンパイル段階でSQLクエリの検証を実行し、デバイス上でアプリケーションを実行する前に構文エラーを発見できるようにします。
重要なポイント
Roomは、Androidプラットフォーム上のローカルSQLiteデータベースの操作を簡素化するためにGoogleが作成したAndroid JetpackのORMライブラリです。データスキーマを記述するためのアノテーションを提供し、コンパイル時にDAOインターフェースの実装を自動生成します。SQLiteOpenHelperの直接使用とは異なり、Roomは開発者をデータベース接続の作成、開封、管理のための大量のボイラープレートコードの記述から解放します。
このライブラリはGoogle I/O 2017でAndroidアーキテクチャコンポーネントの一部として発表されました。それ以来、Roomはローカルデータストレージのデファクトスタンダードとなり、Android向けのGreenDAOやRealmといったソリューションを人気面で上回っています。Googleによると、このライブラリはデバイス上のローカルデータを扱うGoogle Playに公開されているアプリケーションの60%以上で使用されています。
主要な機能は、アノテーションプロセッサを使用したコンパイル時のSQLクエリ検証です。開発者がSQLコマンドでエラーを起こした場合、例えば存在しないカラム名を指定した場合、アプリケーションがインストールされる前にビルドがエラーで失敗します。これは、そのようなエラーが実行時にのみ、多くの場合本番環境で検出されるSQLiteOpenHelperのアプローチとは根本的に異なります。
SQLiteはTEXT、INTEGER、REAL、BLOB、NULLの5つのデータ型のみをサポートしています。しかし、JavaとKotlinではDate、List、Enum、カスタムオブジェクトなどの複雑な型が使用されます。これらを保存するために、RoomはTypeConvertersメカニズムを提供します。これは複雑な型をSQLiteが理解可能なプリミティブ型に変換する静的メソッドです。例えば、DateオブジェクトはLong(タイムスタンプ)に、List<String>はGsonやMoshiを介してJSON文字列に変換されます。
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.build()
コンバーターを宣言するには、静的メソッドに@TypeConverterアノテーションを追加し、データベースレベルで@TypeConvertersアノテーションにコンバータークラスを指定するだけで十分です。Roomは各SQLクエリで該当する型を読み書きする際に自動的にコンバーターを適用し、手動で変換メソッドを呼び出す必要はありません。
RoomはEntity、DAO、Databaseの3つの主要コンポーネントで構成されています。それぞれが厳密に定義された役割を果たし、対応するアノテーションで注釈されます。これらは一緒になって、アプリケーションのビジネスロジックをSQLiteの実装詳細から分離する完全なデータアクセス層を形成します。
Entityはデータベース内の1つのテーブルの構造を記述するデータクラスです。クラスの各フィールドはテーブルのカラムに対応し、データベース内の各行はクラスの1つのインスタンスに対応します。@Entityアノテーションは、クラスがテーブルであることをRoomに通知します。@PrimaryKeyアノテーションが付いたフィールドは主キーを定義し、これは自動インクリメントまたは複合キーにできます。@ForeignKeyはテーブル間のリレーションシップに使用され、データベースレベルでのデータ整合性を保証します。
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "full_name")
val name: String,
val age: Int,
val email: String
)
DAO(Data Access Object)は、データを操作するための操作(挿入、読み取り、更新、削除)を宣言するインターフェースまたは抽象クラスです。各操作は@Insert、@Query、@Update、または@Deleteで注釈されます。Roomはコンパイル時にこのインターフェースの実装を自動生成します。@Queryアノテーションは特に価値があります。SQLクエリを文字列として受け入れ、ビルド時にその正確性を検証します。
@Dao
interface UserDao {
@Insert
suspend fun insert(user: User): Long
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: Int): User?
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
@Delete
suspend fun delete(user: User)
}
DatabaseはRoomDatabaseを拡張する抽象クラスで、データベースへのエントリポイントとして機能します。すべてのEntitiesのリストを含み、DAOを取得するための抽象メソッドを提供します。クラスは@Databaseで注釈され、スキーマバージョンとエンティティのリストを指定します。データベースインスタンスは、アプリケーションコンテキスト、ファイル名、Databaseクラスを指定してRoom.databaseBuilderを介して作成されます。
RoomはSQLiteを置き換えるのではなく、抽象化レイヤーとしてその上で動作します。内部アーキテクチャには、アノテーションプロセッサ、コードジェネレータ、コネクションプールが含まれます。コンパイル時に、アノテーションプロセッサはEntity、DAO、Databaseクラスを分析し、_Implサフィックスを持つ実装クラスを生成します。生成されたすべてのクラスはビルドパッケージに配置され、開発者から直接見えることはありません。
コンパイル時のコード生成はRoomの中核メカニズムです。各DAOインターフェースに対して、注釈されたすべてのメソッドの完全な実装を持つクラスが生成されます。@QueryアノテーションからのSQLクエリは正確性が検証されます。プロセッサはカラム名をEntityフィールドと照合し、SQL構文をチェックします。エラーが見つかった場合、ビルドは明確なメッセージとともに中断されます。これは、エラーが実行時にしか現れない生のSQLiteOpenHelperを使用する場合には不可能です。
生成プロセスには3つの段階が含まれます。第一にスキーマ検証: プロセッサは@Databaseにリストされているすべてのクラスが有効なEntityであることを確認します。第二にDAOボディ生成: 各メソッドに対して、プリペアドクエリを実行する内部RoomSQLiteQueryオブジェクトを使用した実装が作成されます。第三にDatabase_Implクラスの生成: データベースの作成と開封、およびすべてのDAOオブジェクトの初期化を処理します。
class UserDao_Impl(private val __db: RoomDatabase) : UserDao {
private val __insertionAdapter = __db
.createInsertionAdapter(User::class, 0)
override suspend fun insert(user: User): Long {
__db.assertNotSuspendingTransaction()
return __db.runInTransaction {
__insertionAdapter.insertAndReturnId(user)
}
}
}
Roomはデータベース操作用の別個のスレッドプールを作成しません。デフォルトでは、クエリは呼び出し元スレッドで1つの制限付きで実行されます。読み取りと書き込みはスレッドをブロックします。非同期作業のために、Roomはsuspend関数を介してKotlinコルーチンと、戻り値を介してLiveDataと、リアクティブラッパーを介してFlowと統合します。これにより、開発者は特定のタスクに適したアーキテクチャソリューションを選択する柔軟性を得られます。
Roomを使用したメモ保存アプリケーション作成の実践的な例を見てみましょう。アプリケーションには、id、title、content、timestampフィールドを持つ1つのNoteテーブルが含まれています。ユーザーはメモの追加、表示、削除ができます。非同期操作にはコルーチンが使用されます。
AndroidプロジェクトにRoomを統合するには、モジュールレベルのbuild.gradleファイルに依存関係を追加します。Roomには3つのコンポーネントが必要です: ランタイムライブラリ、kaptアノテーションプロセッサ、オプションのコルーチンサポート。ライブラリバージョンは、簡単に更新できるようにroom_version変数で指定されます。Room 2.4.0以降、より高速なビルド速度を提供するkaptの代替としてKSPがサポートされています。
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
// オプション: テスト
testImplementation "androidx.room:room-testing:$room_version"
}
依存関係を設定した後、3つのファイルを作成します: Note Entity、NoteDaoインターフェース、AppDatabaseクラス。Note Entityには@PrimaryKeyと@ColumnInfoアノテーションが付いたフィールドが含まれています。DAOは挿入、リスト取得、削除のためのメソッドを提供します。Databaseは@Databaseアノテーションを介してEntityとDAOを結び付けます。
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String,
@ColumnInfo(name = "created_at")
val timestamp: Long = System.currentTimeMillis()
)
@Dao
interface NoteDao {
@Insert
suspend fun insert(note: Note)
@Query("SELECT * FROM notes ORDER BY created_at DESC")
fun getAllNotes(): Flow<List<Note>>
@Delete
suspend fun delete(note: Note)
}
AppDatabaseファイルはRoomDatabaseを拡張する抽象クラスとして宣言されます。@Databaseアノテーションは現在のバージョンのすべてのEntitiesとスキーマバージョン番号を指定します。インスタンスを取得するには、アプリケーションコンテキストを持つRoom.databaseBuilderのbuildメソッドを介してシングルトンパターンが使用されます。データベースインスタンスをキャッシュすることで、メモリリークを引き起こす可能性のある複数の作成を防ぎます。
マイグレーションはRoomにおいて、既存のデータを失うことなくアプリケーションを更新する際にデータベーススキーマを変更するためのメカニズムです。ユーザーが変更されたEntitiesを含む新しいバージョンをインストールすると、Roomはバージョンの不一致を検出し、指定されたマイグレーション手順を実行します。マイグレーションがない場合、データベースは削除されて再作成され、ユーザーが保存したすべてのデータが失われます。
マイグレーションはMigrationクラスによって記述され、データベースの開始バージョンと終了バージョンを受け取ります。migrateメソッド内で、スキーマを変更するためのALTER TABLEまたはCREATE TABLE SQLクエリが実行されます。Roomはスキーマ変更を自動的に検出できません。開発者はEntityの変更ごとに手動でマイグレーションを記述する必要があります。Room 2.4.0以降、自動マイグレーション生成のための実験的なautoMigrations機能が利用可能です。
autoMigrations機能により、RoomはEntityバージョン間の差分に基づいてマイグレーションを自動生成できます。使用するには、@Databaseに@AutoMigrationアノテーションを追加し、JSONへのスキーマエクスポートを有効にするだけです。Roomは隣接するバージョンのスキーマを比較し、必要なALTERクエリを生成します。ただし、autoMigrationsは後方互換性のある変更のみをサポートします: カラムの追加、インデックスの作成、互換性のある変換による型の変更。
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"ALTER TABLE users ADD COLUMN phone TEXT"
)
}
}
val db = Room
.databaseBuilder(context, AppDatabase::class.java, "app-db")
.addMigrations(MIGRATION_1_2)
.build()
カラム名の変更やテーブルの結合などの複雑な変更には、中間テーブルを使用した手動マイグレーションが必要です。典型的なシナリオ: 古いスキーマで一時テーブルを作成し、変換を加えて古いテーブルから新しいテーブルにデータをコピーし、古いテーブルを削除して一時テーブルの名前を変更します。Roomはすべてのマイグレーションが単一のトランザクションで実行されることを保証し、エラーが発生した場合、変更は完全にロールバックされます。
よくある質問
Roomはアノテーションとコンパイル時のSQL検証を備えたORM抽象化を提供しますが、SQLiteOpenHelperではすべてのクエリを手動で記述し、接続を管理する必要があります。RoomはCRUD操作用のコードを自動生成し、LiveDataやFlowを含むAndroidアーキテクチャコンポーネントと統合します。
RoomはすべてのJavaプリミティブ型(Int、Long、Boolean、Float、Double)に加え、String、ByteArray、Dateをサポートしています。ListやEnumなどの複雑な型には、TypeConvertersが使用されます。これは非標準型をSQLite互換フォーマットに変換する静的変換メソッドです。
はい、Roomはコルーチンなしの同期呼び出しをサポートしていますが、実行中のスレッドをブロックします。非同期作業には、コルーチンの代わりにLiveDataまたはRxJavaを使用できます。Googleは新しいプロジェクトでは、非同期データアクセスの主要な方法としてコルーチンを使用することを推奨しています。
Roomがデータベースバージョンの不一致を検出し、適切なマイグレーションを見つけられない場合、デフォルトではエラーの説明とともにIllegalStateExceptionがスローされます。開発者はfallbackToDestructiveMigrationメソッドでこの動作をオーバーライドでき、既存のデータベースを削除して新しいものを作成し、すべてのデータが失われます。
Roomは@Embeddedアノテーションを使用したネストされたオブジェクトと、@Relationアノテーションを使用したリレーションクラスを通じてリレーションシップをサポートします。テーブル結合を伴う複雑なクエリには、カスタムPOJOクラスが使用され、そのフィールドはSQL JOIN文を含む@Queryの結果から入力されます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。