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

Room 永続ライブラリを使用してアプリのデータを保存する場合、データアクセス オブジェクト(DAO)を定義して、保存対象のデータを操作します。各 DAO は、アプリのデータベースへの抽象アクセスを可能にするメソッドを備えています。コンパイル時に、Room は定義した DAO の実装を自動的に生成します。

クエリビルダーやダイレクト クエリではなく DAO を使用してアプリのデータベースにアクセスすることで、重要なアーキテクチャ原則である関心の分離を維持できます。DAO を使用することで、アプリをテストするときにデータベース アクセスをモックすることもできます。

DAO の仕組み

各 DAO は、インターフェースまたは抽象クラスとして定義できます。基本的なユースケースでは、通常はインターフェースを使用します。いずれの場合も、DAO には常に @Dao アノテーションを付ける必要があります。DAO にプロパティはありませんが、アプリのデータベース内のデータを操作する 1 つ以上のメソッドを定義します。

次のコードは、Room データベースで User オブジェクトを挿入、削除、選択するメソッドを定義する、シンプルな DAO の例です。

Kotlin

@Dao
interface UserDao {
    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)

    @Query("SELECT * FROM user")
    fun getAll(): List<User>
}

Java

@Dao
public interface UserDao {
    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);

    @Query("SELECT * FROM user")
    List<User> getAll();
}

データベース操作を定義する DAO メソッドには、次の 2 種類があります。

  • SQL コードを記述せずにデータベースの行を挿入、更新、削除できるコンビニエンス メソッド。
  • データベースを操作するための独自の SQL クエリを記述できるクエリメソッド。

以降のセクションでは、アプリに必要なデータベース操作を定義するために両方のタイプの DAO メソッドを使用する方法を示します。

コンビニエンス メソッド

Room には、SQL ステートメントを記述せずにシンプルな挿入、更新、削除を行うメソッドを定義するための便利なアノテーションが用意されています。

より複雑な挿入、更新、削除を定義する必要がある場合や、データベース内のデータにクエリを行う必要がある場合は、代わりにクエリメソッドを使用します。

挿入

@Insert アノテーションを使用すると、データベース内の適切なテーブルにパラメータを挿入するメソッドを定義できます。次のコードは、1 つ以上の User オブジェクトをデータベースに挿入する有効な @Insert メソッドの例を示しています。

Kotlin

@Dao
interface UserDao {
    @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 UserDao {
    @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 メソッドの各パラメータは、@Entity アノテーションが付けられた Room データ エンティティ クラスのインスタンスか、データベースを参照しているデータ エンティティ クラスのインスタンスのコレクションである必要があります。@Insert メソッドが呼び出されると、Room は、渡された各エンティティ インスタンスを、対応するデータベース テーブルに挿入します。

@Insert メソッドが単一のパラメータを受け取る場合、long 値(挿入されたアイテムの新しい rowId)を返すことができます。パラメータが配列またはコレクションの場合、代わりに long 値の配列またはコレクションを返します。各値は挿入されたアイテムのいずれかの rowId となります。rowId 値の返し方の詳細については、@Insert アノテーションのリファレンス ドキュメントと、rowid テーブルの SQLite ドキュメントをご覧ください。

更新

@Update アノテーションを使用すると、データベース テーブルの特定の行を更新するメソッドを定義できます。@Insert メソッドと同様に、@Update メソッドはデータ エンティティ インスタンスをパラメータとして受け取ります。次のコードは、データベース内の 1 つ以上の User オブジェクトを更新しようとする @Update メソッドの例を示しています。

Kotlin

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

Java

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

Room は主キーを使用して、渡されたエンティティ インスタンスをデータベースの行と照合します。同じ主キーを持つ行がない場合、Room は変更を行いません。

@Update メソッドは、必要に応じて、正常に更新された行数を示す int 値を返すことができます。

削除

@Delete アノテーションを使用すると、データベース テーブルから特定の行を削除するメソッドを定義できます。@Insert メソッドと同様に、@Delete メソッドはデータ エンティティ インスタンスをパラメータとして受け取ります。次のコードは、データベースから 1 つ以上の User オブジェクトを削除しようとする @Delete メソッドの例を示しています。

Kotlin

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

Java

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

Room は主キーを使用して、渡されたエンティティ インスタンスをデータベースの行と照合します。同じ主キーを持つ行がない場合、Room は変更を行いません。

@Delete メソッドは、必要に応じて、正常に削除された行数を示す int 値を返すことができます。

クエリメソッド

@Query アノテーションを使用すると、SQL ステートメントを記述して DAO メソッドとして公開できます。これらのクエリメソッドは、アプリのデータベースからデータをクエリする場合や、より複雑な挿入、更新、削除を行う必要がある場合に使用できます。

Room はコンパイル時に SQL クエリを検証します。つまり、クエリに問題がある場合、ランタイム エラーではなくコンパイル エラーが発生します。

シンプルなクエリ

次のコードは、シンプルな SELECT クエリを使用してデータベース内のすべての User オブジェクトを返すメソッドを定義しています。

Kotlin

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

Java

@Query("SELECT * FROM user")
public User[] loadAllUsers();

以降のセクションでは、一般的なユースケースに合わせてこの例を変更する方法を示します。

テーブルの列のサブセットを返す

ほとんどの場合、クエリを行うテーブルの列のサブセットを返すだけで済みます。たとえば、あるユーザーに関する詳細情報をすべて UI に表示するのではなく、氏名のみを表示する場合があります。リソースを節約してクエリの実行を効率化するには、必要なフィールドのみをクエリします。

Room では、結果列のセットを返されるオブジェクトにマッピングできる限り、どのようなクエリからもシンプルなオブジェクトを返すことができます。たとえば、ユーザーの氏名を保持する次のオブジェクトを定義できます。

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;
}

その後、クエリメソッドからシンプルなオブジェクトを返すことができます。

Kotlin

@Query("SELECT first_name, last_name FROM user")
fun loadFullName(): List<NameTuple>

Java

@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();

Room は、このクエリが first_name 列と last_name 列の値を返し、その値を NameTuple クラスのフィールドにマッピングできることを認識します。返されたオブジェクトのフィールドにマッピングされない列をクエリが返した場合、Room は警告を表示します。

クエリにシンプルなパラメータを渡す

ほとんどの場合、DAO メソッドは、フィルタ処理を行えるようにパラメータを受け入れる必要があります。Room では、メソッド パラメータをクエリでバインド パラメータとして使用することをサポートしています。

たとえば、次のコードは、特定の年齢以上のユーザーをすべて返すメソッドを定義しています。

Kotlin

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

Java

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

次のコードに示すように、1 回のクエリで複数のパラメータを渡すことも、同じパラメータを複数回参照することもできます。

Kotlin

@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

@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);

クエリにパラメータのコレクションを渡す

DAO メソッドによっては、実行時までわからない可変数のパラメータを渡すことが必要になる場合があります。Room は、パラメータがコレクションを示している場合はそのことを認識し、供給されたパラメータの数に基づいて実行時に自動的にコレクションを展開します。

たとえば、次のコードは、リージョンのサブセットからすべてのユーザーに関する情報を返すメソッドを定義しています。

Kotlin

@Query("SELECT * FROM user WHERE region IN (:regions)")
fun loadUsersFromRegions(regions: List<String>): List<User>

Java

@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);

複数のテーブルにクエリする

クエリによっては、結果を計算するために複数のテーブルにアクセスすることが必要になる場合があります。SQL クエリで JOIN 句を使用すると、複数のテーブルを参照できます。

次のコードは、3 つのテーブルを結合して特定のユーザーに現在貸し出されている本を返すメソッドを定義しています。

Kotlin

@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

@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);

テーブルの列のサブセットを返すセクションで説明したように、結合された複数のテーブルから列のサブセットを返すシンプルなオブジェクトを定義することもできます。次のコードは、ユーザー名とユーザーが借りた本の名前を返すメソッドを持つ DAO を定義しています。

Kotlin

interface UserBookDao {
    @Query(
        "SELECT user.name AS userName, book.name AS bookName " +
        "FROM user, book " +
        "WHERE user.id = book.user_id"
    )
    fun loadUserAndBookNames(): LiveData<List<UserBook>>

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

Java

@Dao
public interface UserBookDao {
   @Query("SELECT user.name AS userName, book.name AS bookName " +
          "FROM user, book " +
          "WHERE user.id = book.user_id")
   public LiveData<List<UserBook>> loadUserAndBookNames();

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

マルチマップを返す

Room 2.4 以降では、マルチマップを返すクエリメソッドを作成して、追加のデータクラスを定義せずに複数のテーブルの列をクエリすることもできます。

複数のテーブルにクエリするのセクションの例で考えてみます。User インスタンスと Book インスタンスのペアを保持するカスタム データクラスのインスタンスのリストを返す代わりに、UserBook のマッピングをクエリメソッドから直接返すことができます。

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<User, List<Book>> loadUserAndBookNames();

クエリメソッドがマルチマップを返す場合、GROUP BY 句を使用したクエリを作成することで、高度な計算やフィルタリングを行うために SQL の機能を利用できます。たとえば、本を 3 冊以上借りたユーザーのみを返すように loadUserAndBookNames() メソッドを変更できます。

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id" +
    "GROUP BY user.name WHERE COUNT(book.id) >= 3"
)
public Map<User, List<Book>> loadUserAndBookNames();

オブジェクト全体をマッピングする必要がない場合は、クエリメソッドの @MapInfo アノテーションで keyColumn 属性と valueColumn 属性を設定することで、クエリ内の特定の列間のマッピングを返すこともできます。

Kotlin

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<String, List<String>>

Java

@MapInfo(keyColumn = "userName", valueColumn = "bookName")
@Query(
    "SELECT user.name AS username, book.name AS bookname FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<String, List<String>> loadUserAndBookNames();

特別な戻り値の型

Room には、他の API ライブラリと統合するための特別な戻り値の型が用意されています。

ページング ライブラリによるページングされたクエリ

Room は、ページング ライブラリとの統合により、ページングされたクエリをサポートしています。Room 2.3.0-alpha01 以降では、DAO は Paging 3 で使用する PagingSource オブジェクトを返すことができます。

Kotlin

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  fun pagingSource(query: String): PagingSource<Int, User>
}

Java

@Dao
interface UserDao {
  @Query("SELECT * FROM users WHERE label LIKE :query")
  PagingSource<Integer, User> pagingSource(String query);
}

PagingSource の型パラメータの選択について詳しくは、キーと値の型を選択するをご覧ください。

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

アプリのロジックで戻り値の行に直接アクセスする必要がある場合は、次の例に示すように、Cursor オブジェクトを返すように DAO メソッドを作成できます。

Kotlin

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

Java

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

参考情報

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

サンプル

Codelabs