使用 Room DAO 存取資料

Stay organized with collections Save and categorize content based on your preferences.

使用 Room 永久性程式庫來儲存應用程式資料時,開發人員可以透過定義資料存取物件 (DAO),進而與儲存的資料互動。每個 DAO 都提供許多方法,以抽象存取應用程式資料庫。在編譯期間,Room 會自動以開發人員定義的 DAO 進行實作。

透過 DAO 存取應用程式資料庫,而非查詢建構工具或直接查詢,開發人員就能維持關注點分離 (SoC)原則,這是非常重要的架構原則。DAO 也可讓測試應用程式時,更輕鬆地模擬存取資料庫。

DAO 剖析

每個 DAO 都可以定義為介面或抽象類別。如果是基本功能,通常會定義為介面。無論採取何種定義,一律必須使用 @Dao 註解 DAO。DAO 本身沒有屬性,但在與應用程式資料庫互動方面,提供了一種或多種方法。

如要瞭解如何在 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 有有兩種方法用來定義資料庫互動:

  • 可以不用寫入任何 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 方法的每項參數都必須是 Room 資料實體類別的執行個體 (須註解 @Entity),或是資料實體執行個體的集合。呼叫 @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();

如要針對一般用途修改此範例,請見以下說明。

回傳部分資料欄

大多數情況下,開發人員只需查詢的資料表傳回一小部分資料欄。例如,您的使用者介面只顯示使用者的名字和姓氏,不需要該使用者的所有資料。為節省資源並簡化查詢,您只需要查詢所需欄位即可。

只要開發人員能夠將結果資料欄集合對應至傳回的物件,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_namelast_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 及以上的版本中,開發人員無須額外定義資料類別,就可以查詢多份資料表的資料欄,只要編寫能傳回多重映射的查詢方法即可。

詳情請參閱「查詢多份資料表」一節中的範例。開發人員可以使用自己的查詢方法,直接傳回對應的 UserBook,而無須傳回含有 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 功能進行進階計算和篩選。比方說,開發人員可以修改 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 註解設定 keyColumnvalueColumn 屬性即可:

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 會傳回 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 存取資料,請參閱以下資源:

範例

程式碼研究室