使用 Room 持續性程式庫儲存應用程式資料時,開發人員可以透過定義「資料存取物件」(或稱 DAO)與儲存的資料互動。每個 DAO 都包含許多方法,可用來提供應用程式資料庫的抽象存取權。在編譯期間,Room 會自動產生您定義的 DAO 實作成果。
透過 DAO 存取應用程式資料庫,而不使用查詢建立工具或直接查詢,可讓您維持關注點分離這項重要的架構原則。DAO 也可讓您在測試應用程式時,更輕鬆地模擬資料庫存取程序。
DAO 剖析
您可以將每個 DAO 定義為介面或抽象類別。如果是基本用途,通常應定義為介面。不論是何種用途,您都必須使用 @Dao
為 DAO 加註。DAO 沒有屬性,但會定義一或多項方法,用來與您應用程式資料庫的資料互動。
以下程式碼為簡單的 DAO 定義方法示例,用於在 Room 資料庫中插入、刪除及選取 User
物件。
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 方法類型可定義資料庫互動:
- 便利方法:不必編寫任何 SQL 程式碼,即可在資料庫中插入、更新及刪除資料列。
- 查詢方法:可讓您編寫自己的 SQL 查詢,以與資料庫互動。
接下來的章節會說明如何使用這兩種 DAO 方法,定義應用程式需要的資料庫互動。
便利方法
Room 提供的便利註解可用來定義方法,讓您不必編寫 SQL 陳述式即可執行簡單的插入、更新和刪除作業。
如果您需要定義更複雜的插入、更新或刪除作業,或是需要查詢資料庫中的資料,請改用查詢方法。
插入
@Insert
註解可定義將參數插入資料庫中適合資料表的方法。以下程式碼顯示有效的 @Insert
方法示例,可將一或多個 User
物件插入資料庫:
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
方法接受以資料實體例項做為參數。以下程式碼顯示 @Update
方法示例,嘗試更新資料庫中的一或多個 User
物件。
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
方法接受以資料實體例項做為參數。以下程式碼顯示 @Delete
方法示例,嘗試從資料庫中刪除一或多個 User
物件:
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);
您也可以在同一個查詢中傳遞多個參數,或是多次參照相同的參數,如以下程式碼所示:
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
子句來參照多份資料表。
以下程式碼定義的方法會結合三份資料表,將目前外借中的書籍資訊傳回給特定使用者:
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 功能進行進階計算和篩選。舉例來說,您可以修改 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 程式庫整合。
使用 Paging 程式庫進行分頁查詢
Room 藉由與 Paging 程式庫整合來支援分頁查詢功能。在 Room 2.3.0-alpha01 以上版本中,DAO 可以傳回 PagingSource
物件,以便與 Paging 3 搭配使用。
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
選擇類型參數,請參閱「選取鍵和值類型」。
直接的游標存取權
如果應用程式的邏輯要求直接存取傳回的資料列,您可以編寫 DAO 方法以傳回 Cursor
物件,如以下範例所示:
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 存取資料,請參閱以下其他資源: