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
インスタンスのペアを保持するカスタム データクラスのインスタンスのリストを返す代わりに、User
と Book
のマッピングをクエリメソッドから直接返すことができます。
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 を使用してデータにアクセスする方法について詳しくは、以下の参考情報をご確認ください。