Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

Room DAO を使用してデータにアクセスする

Room 永続ライブラリを使用してアプリのデータにアクセスするには、データ アクセス オブジェクト(DAO)を使用します。この Dao オブジェクト セットは、Room の主要コンポーネントであり、各 DAO は、アプリのデータベースへの抽象アクセスを可能にするメソッドを備えています。

クエリビルダーやダイレクト クエリではなく DAO クラスを使用してデータベースにアクセスすることで、データベース アーキテクチャの各種コンポーネントを分離できます。 また、DAO を使用すると、アプリをテストする際、簡単にデータベース アクセスを模倣できます。

DAO は、インターフェース クラスとしても抽象クラスとしても利用できます。抽象クラスの場合、必要に応じて、唯一のパラメータとして RoomDatabase を取るコンストラクタを指定できます。コンパイル時に Room によって各 DAO 実装が作成されます。

注: UI が長期間ロックされる可能性があるため、ビルダーで allowMainThreadQueries() を呼び出していない限り、Room では、メインスレッド上でのデータベース アクセスはサポートされていません。非同期クエリ(LiveData または Flowable のインスタンスを返すクエリ)は、必要に応じてバックグラウンド スレッドで非同期にクエリを実行するため、このルールから除外されます。

コンビニエンス メソッドを定義する

DAO クラスを使用すると、さまざまなコンビニエンス クエリを表現できます。 このドキュメントでは、よく利用される例について説明します。

挿入

DAO メソッドを作成して @Insert のアノテーションを付けた場合、すべてのパラメータを単一のトランザクション内でデータベースに挿入する実装が Room によって作成されます。

クエリの例を次のコード スニペットに示します。

Kotlin

    @Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insertUsers(vararg users: User)

        @Insert
        fun insertBothUsers(user1: User, user2: User)

        @Insert
        fun insertUsersAndFriends(user: User, friends: List<User>)
    }
    

Java

    @Dao
    public interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        public void insertUsers(User... users);

        @Insert
        public void insertBothUsers(User user1, User user2);

        @Insert
        public void insertUsersAndFriends(User user, List<User> friends);
    }
    

@Insert メソッドが 1 つのパラメータだけを受け取る場合、挿入されるアイテムの新しい rowId になる long を返すことができます。パラメータが配列やコレクションの場合は、long[]List<Long> が返されます。

詳細については、@Insert アノテーションのリファレンス ドキュメントと rowid テーブルの SQLite ドキュメントをご覧ください。

更新

Update コンビニエンス メソッドは、パラメータとして指定されたデータベース内のエンティティのセットを変更します。このメソッドは、各エンティティの主キーをマッチングするクエリを使用します。

このメソッドを定義する方法を次のコード スニペットに示します。

Kotlin

    @Dao
    interface MyDao {
        @Update
        fun updateUsers(vararg users: User)
    }
    

Java

    @Dao
    public interface MyDao {
        @Update
        public void updateUsers(User... users);
    }
    

通常は必要ありませんが、データベース内で更新された行数を示す int 値をこのメソッドで返すこともできます。

削除

Delete コンビニエンス メソッドは、パラメータとして指定されたデータベース内のエンティティのセットを削除します。このメソッドは、主キーを使用して、削除するエンティティを検索します。

このメソッドを定義する方法を次のコード スニペットに示します。

Kotlin

    @Dao
    interface MyDao {
        @Delete
        fun deleteUsers(vararg users: User)
    }
    

Java

    @Dao
    public interface MyDao {
        @Delete
        public void deleteUsers(User... users);
    }
    

通常は必要ありませんが、データベースから削除された行数を示す int 値をこのメソッドで返すこともできます。

情報のクエリ

@Query は、DAO クラス内で使用される主要なアノテーションです。データベースに対して読み書き処理を実行できます。各 @Query メソッドはコンパイル時に検証されるため、クエリに問題があると、ランタイム エラーの代わりにコンパイル エラーが発生します。

Room はクエリの戻り値についても検証を行い、戻りオブジェクトのフィールド名がクエリ応答内の対応列名と一致しない場合、次の 2 つの方法のいずれかでアラートを通知します。

  • 一部のフィールド名だけが一致している場合は、警告を表示します。
  • フィールド名が 1 つも一致しない場合は、エラーを表示します。

シンプルなクエリ

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user")
        fun loadAllUsers(): Array<User>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user")
        public User[] loadAllUsers();
    }
    

上記は、すべてのユーザーを読み込む非常にシンプルなクエリです。Room はコンパイル時に、user テーブル内のすべての列をクエリしていることを認識します。クエリ内に構文エラーが含まれている場合や、データベース内に user テーブルが存在しない場合、Room はアプリのコンパイル時にエラー メッセージを表示します。

クエリにパラメータを渡す

たとえば、特定の年齢よりも年上のユーザーだけを表示するなど、フィルタリング処理を実行する際はほとんどの場合、パラメータをクエリに渡す必要があります。このタスクを実現するには、Room アノテーション内でメソッド パラメータを使用します。次のコード スニペットをご覧ください。

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge")
        fun loadAllUsersOlderThan(minAge: Int): Array<User>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge")
        public User[] loadAllUsersOlderThan(int minAge);
    }
    

コンパイル時にこのクエリを処理する際、Room は :minAge バインド パラメータと minAge メソッド パラメータをマッチングします。Room は、パラメータ名を使用してマッチングを実行します。不一致が存在する場合、アプリのコンパイル時にエラーが発生します。

1 回のクエリ内で複数のパラメータを渡すことも、それを複数回参照することもできます。次のコード スニペットをご覧ください。

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
        fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User>

        @Query("SELECT * FROM user WHERE first_name LIKE :search " +
               "OR last_name LIKE :search")
        fun findUserWithName(search: String): List<User>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
        public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

        @Query("SELECT * FROM user WHERE first_name LIKE :search " +
               "OR last_name LIKE :search")
        public List<User> findUserWithName(String search);
    }
    

列のサブセットを返す

ほとんどの場合、取得する必要があるのは、エンティティの一部のフィールドだけです。たとえば、UI に表示するのは、ユーザーのすべての詳細情報ではなく、ユーザーの姓名だけという場合があります。このような場合に、アプリの UI に表示する列だけを取得することで、貴重なリソースを節約し、クエリを迅速に完了することができます。

Room では、結果列のセットを戻りオブジェクトにマッピングできる限り、クエリから Java ベースのオブジェクトを返すことができます。たとえば、次のプレーン オールド Java ベース オブジェクト(POJO)を作成することで、ユーザーの姓名を取得できます。

Kotlin

    data class NameTuple(
        @ColumnInfo(name = "first_name") val firstName: String?,
        @ColumnInfo(name = "last_name") val lastName: String?
    )
    

Java

    public class NameTuple {
        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        @NonNull
        public String lastName;
    }
    

この POJO は、次のクエリメソッドで使用できます。

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        fun loadFullName(): List<NameTuple>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user")
        public List<NameTuple> loadFullName();
    }
    

Room は、このクエリが first_name 列と last_name 列の値を返し、その値を NameTuple クラスのフィールドにマッピングできることを認識します。その結果、Room は適切なコードを生成できます。クエリが返す列数が多すぎる場合や、NameTuple クラス内に存在しない列を返した場合は、警告が表示されます。

引数のコレクションを渡す

クエリによっては、実行時までパラメータの正確な数がわからず、数が可変のパラメータを渡すことが必要になる場合があります。たとえば、地域のサブセットからすべてのユーザーに関する情報を取得することがあります。Room は、パラメータがコレクションを示している場合はそのことを認識し、供給されたパラメータの数に基づいて実行時に自動的にコレクションを展開します。

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        fun loadUsersFromRegions(regions: List<String>): List<NameTuple>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public List<NameTuple> loadUsersFromRegions(List<String> regions);
    }
    

ダイレクト カーソル アクセス

アプリのロジックが戻り行に直接アクセスする必要がある場合は、クエリから Cursor オブジェクトを返します。次のコード スニペットをご覧ください。

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
        fun loadRawUsersOlderThan(minAge: Int): Cursor
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
        public Cursor loadRawUsersOlderThan(int minAge);
    }
    

注: Cursor API を使用する方法はおすすめしません。Cursor API では、行が存在するかどうか、行にどのような値が含まれているのかを知ることができません。この機能を使用するのは、カーソルを必要とし、簡単にはリファクタリングできないコードがすでに存在する場合に限るようにしてください。

複数のテーブルのクエリを行う

クエリによっては、結果を計算するために複数のテーブルにアクセスすることが必要になる場合があります。Room の場合、任意のクエリを記述することが可能で、テーブルを結合することもできます。 また、応答が監視可能なデータ型(FlowableLiveData など)である場合、Room は、クエリ内で参照されているすべてのテーブルを対象に、無効なものがないかモニタリングします。

テーブル結合を実行して、書籍を借りているユーザーを格納しているテーブルと現在貸出中の書籍に関するデータを格納しているテーブルの情報を統合する方法を、次のコード スニペットに示します。

Kotlin

    @Dao
    interface MyDao {
        @Query(
            "SELECT * FROM book " +
            "INNER JOIN loan ON loan.book_id = book.id " +
            "INNER JOIN user ON user.id = loan.user_id " +
            "WHERE user.name LIKE :userName"
        )
        fun findBooksBorrowedByNameSync(userName: String): List<Book>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * FROM book " +
               "INNER JOIN loan ON loan.book_id = book.id " +
               "INNER JOIN user ON user.id = loan.user_id " +
               "WHERE user.name LIKE :userName")
       public List<Book> findBooksBorrowedByNameSync(String userName);
    }
    

また、このようなクエリから POJO を返すこともできます。たとえば、次のようにユーザーとペットの名前を読み込むクエリを記述できます。

Kotlin

    @Dao
    interface MyDao {
        @Query(
            "SELECT user.name AS userName, pet.name AS petName " +
            "FROM user, pet " +
            "WHERE user.id = pet.user_id"
        )
        fun loadUserAndPetNames(): LiveData<List<UserPet>>

        // You can also define this class in a separate file.
        data class UserPet(val userName: String?, val petName: String?)
    }
    

Java

    @Dao
    public interface MyDao {
       @Query("SELECT user.name AS userName, pet.name AS petName " +
              "FROM user, pet " +
              "WHERE user.id = pet.user_id")
       public LiveData<List<UserPet>> loadUserAndPetNames();

       // You can also define this class in a separate file, as long as you add the
       // "public" access modifier.
       static class UserPet {
           public String userName;
           public String petName;
       }
    }
    

クエリの戻り値の型

Room は、特定のフレームワークや API との相互運用のための特殊な戻り値の型など、クエリメソッドのさまざまな戻り値の型をサポートしています。次の表に、クエリタイプとフレームワークに基づいて、該当する戻り値の型を示します。

クエリタイプ コルーチン RxJava Guava Lifecycle
監視可能な読み取り Flow<T> Flowable<T>Publisher<T>Observable<T> なし LiveData<T>
ワンショット読み取り suspend fun Single<T>Maybe<T> ListenableFuture<T> なし
ワンショット書き込み suspend fun Single<T>Maybe<T>Completable<T> ListenableFuture<T> なし

フローを使用したリアクティブ クエリ

Room 2.2 以降では、Kotlin の Flow 機能を使用して、アプリの UI を最新の状態に保つことができます。基になるデータが変更されたときに UI を自動的に更新するには、Flow オブジェクトを返すクエリメソッドを記述します。

@Query("SELECT * FROM User")
    fun getAllUsers(): Flow<List<User>>
    

テーブル内のデータが変更されるたびに、返された Flow オブジェクトは再びクエリをトリガーし、結果セット全体が再出力されます。

Flow を使用したリアクティブ クエリには、1 つの重要な制限があります。それは、テーブル内の行が更新されるたびに、その行が結果セットに含まれているかどうかにかかわらず、Flow オブジェクトはクエリを再実行するということです。実際のクエリ結果が変更された場合にのみ UI が通知されるようにするには、返された Flow オブジェクトに distinctUntilChanged() 演算子を適用します。

@Dao
    abstract class UsersDao {
        @Query("SELECT * FROM User WHERE username = :username")
        abstract fun getUser(username: String): Flow<User>

        fun getUserDistinctUntilChanged(username:String) =
               getUser(username).distinctUntilChanged()
    }
    

Kotlin コルーチンを使用した非同期クエリ

DAO メソッドに suspend Kotlin キーワードを追加すると、Kotlin コルーチン機能を使用して非同期化できます。これにより、メインスレッド上で実行される可能性がなくなります。

@Dao
    interface MyDao {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        suspend fun insertUsers(vararg users: User)

        @Update
        suspend fun updateUsers(vararg users: User)

        @Delete
        suspend fun deleteUsers(vararg users: User)

        @Query("SELECT * FROM user")
        suspend fun loadAllUsers(): Array<User>
    }
    

この方法は、@Transaction アノテーション付きの DAO メソッドにも当てはまります。この機能を使用して、別の DAO メソッドから一時停止データベース メソッドを作成できます。いずれのメソッドも、単一のデータベース トランザクション内で実行されます。

@Dao
    abstract class UsersDao {
        @Transaction
        open suspend fun setLoggedInUser(loggedInUser: User) {
            deleteUser(loggedInUser)
            insertUser(loggedInUser)
        }

        @Query("DELETE FROM users")
        abstract fun deleteUser(user: User)

        @Insert
        abstract suspend fun insertUser(user: User)
    }
    

アプリ内で Kotlin コルーチンを使用する方法については、Kotlin コルーチンを使用してアプリのパフォーマンスを改善するをご覧ください。

LiveData を使用した監視可能なクエリ

クエリを実行しているとき、データの変更に応じてアプリの UI を自動的に更新したいことがよくあります。これを実現するには、クエリメソッドの説明で LiveData 型の戻り値を使用します。データベースが更新されると、LiveData の更新に必要なすべてのコードが Room によって生成されます。

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
        public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
    }
    

RxJava を使用したリアクティブ クエリ

Room は、RxJava2 型の戻り値を以下のようにサポートしています。

  • @Query メソッド: Room は、PublisherFlowableObservable 型の戻り値をサポートしています。
  • @Insert メソッド、@Update メソッド、@Delete メソッド: Room 2.1.0 以降は、CompletableSingle<T>Maybe<T> 型の戻り値をサポートしています。

この機能を使用するには、最新バージョンの rxjava2 アーティファクトをアプリの build.gradle ファイル内に組み込みます。

app/build.gradle

    dependencies {
        def room_version = "2.1.0"
        implementation 'androidx.room:room-rxjava2:$room_version'
    }
    

このライブラリの現在のバージョンを確認するには、バージョンのページで Room に関する情報をご覧ください。

このような戻り値の型の使用例を次のコード スニペットに示します。

Kotlin

    @Dao
    interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        fun loadUserById(id: Int): Flowable<User>

        // Emits the number of users added to the database.
        @Insert
        fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int>

        // Makes sure that the operation finishes successfully.
        @Insert
        fun insertLargeNumberOfUsers(varargs users: User): Completable

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        fun deleteAllUsers(users: List<User>): Single<Int>
    }
    

Java

    @Dao
    public interface MyDao {
        @Query("SELECT * from user where id = :id LIMIT 1")
        public Flowable<User> loadUserById(int id);

        // Emits the number of users added to the database.
        @Insert
        public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);

        // Makes sure that the operation finishes successfully.
        @Insert
        public Completable insertLargeNumberOfUsers(User... users);

        /* Emits the number of users removed from the database. Always emits at
           least one user. */
        @Delete
        public Single<Integer> deleteUsers(List<User> users);
    }
    

詳細については、Google Developers の Room および RxJava をご覧ください。

参考情報

Room DAO を使用してデータにアクセスする方法について詳しくは、以下の参考情報をご確認ください。

サンプル

コードラボ

ブログ