要使用 Room 存取資料,需要使用 DAO。
這組 DAO 物件形成了 Room 的主要組件,因為每個 DAO 都包含提供對資料庫的抽象訪問方法。
通過使用 DAO 類別而不是查詢構建器或直接查詢來訪問資料庫,使用者可以分離出資料庫架構的不同元件。
此外,DAO 可在測試應用程序時輕鬆模擬資料庫訪問。
注意:
在加入 DAO 類別之前,先在 app 的 build.gradle 加入相依性。
DAO 可以是 interface,也可以是抽象類別。如果是抽象類別,它可以選擇有一個構造函數,它將 RoomDatabase 作為唯一的參數。Room 在編譯時期建立每個 DAO 實作。
注意:
Room 並不支援在 Main thread 上存取資料庫,因為可能會長時間鎖定 UI。
如果要讓 Room 支援在其他執行緒存取資料庫則必須在建構時呼叫allowMainThreadQueries() 方法。
異步查詢 – 若是查詢會返回 LiveData 或 Flowable 實例 – 不受此規則的約束,因為它們在需要時在後台線程上異步運行查詢
 

Define methods for convenience

可以使用 DAO 類別表示多個便捷查詢。

Insert

當建立 DAO 方法並使用 @Insert 註釋時,Room 會生成一個實現,該實現在單一事務(single transaction)中將所有參數插入到資料庫中。

@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 個參數,則它可以返回 long,這是該插入項目的新 rowId。如果參數是陣列或集合,則應返回 long [] 或 List <Long>。
有關更多詳細信息,請參閱 @Insert 的文件,以及 SQLite documenation for rowid tables

Update

Update 修改資料庫中作為參數給出的一組實體。它使用每個實體的主鍵做查詢。

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

雖然通常沒有必要,但可以讓此方法返回一個 int 值,表示資料庫中更新的行數。
 

Delete

Delete 從資料庫中刪除一組從參數傳入的實體。使用主鍵來查找要刪除的實體。

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

雖然通常沒有必要,但可以讓此方法返回一個 int 值,表示從資料庫中刪除的行數。
 

Query for information

@Query 是 DAO 類別中使用的主要註釋。它可以對資料庫執行讀/寫操作。
每個 @Query 方法都在編譯時進行驗證,因此如果查詢出現問題,則會發生編譯錯誤而不是運行時失敗。
Room 也會驗證查詢的返回值,避免返回的物件中的屬性名稱與查詢的相應列名稱不相符,Room 使用以下兩個方法之一

  • 如果只有一些屬性名稱相符,它會發出警告。
  • 如果沒有屬性名稱相符,則會發出錯誤。

Simple queries

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

以上是一個非常簡單的查詢,可以取得所有用戶。在編譯時,Room 知道正在查詢用戶表中的所有列。如果查詢包含語法錯誤,或者資料庫中不存在該資料表,則在應用程序編譯時,Room 會顯示包含相應消息的錯誤。

Passing parameters into the query

大多數情況下,需要將參數傳遞給查詢以執行過濾操作,例如僅顯示年齡超過特定年齡的用戶。
要傳入參數,請在 Room 註釋中使用方法參數,如以下代碼段所示

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

在編譯時處理此查詢時,Room 會將:minAge 綁定參數與 minAge 方法的參數。
Room 使用參數名稱檢查是否相符。如果存在不相符,則在應用編譯時會發生錯誤。
還可以在查詢中多次傳遞多個參數或引用它們,如以下代碼段所示:

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

 

Returning subsets of columns

大多數情況下,只需要獲得實體的幾個屬性。
例如,UI 可能只顯示用戶的名字和姓氏,而不是用戶的每一個訊息。
通過僅提取 UI 顯示的列,可以節省寶貴的資源,並且讓查詢可以更快地完成。
Room 允許從查詢中返回任何基於 Java 的對象,只要結果可以映射到返回的對象即可。
例如,可以建立以下(POJO)來獲取用戶的名字和姓氏

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

現在,可以在查詢方法中使用此 POJO:

@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 會顯示警告。
注意:
這些 POJO 也可以使用 @Embedded 註釋。
 

Passing a collection of arguments

某些查詢可能要求傳入可變數量的參數,並且在運行時之前不知道參數的確切數量。
例如,可能希望從區域子集中檢索有關所有用戶的信息。
Room 了解參數何時表示集合,並根據提供的參數數量在運行時自動擴展它。

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

 

Observable queries

執行查詢時,通常希望應用程序的UI在資料更改時自動更新。
要實現此目的,請在查詢方法描述中使用 LiveData 類型的返回值。
Room 會產生所有必要的代碼,以便在更新資料庫時更新 LiveData。

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

注意:
從版本 1.0 開始,Room 使用查詢中訪問的表列表來決定是否更新 LiveData 的實例。
 

Reactive queries with RxJava

Room 為 RxJava2 類型的返回值提供以下支持:

  • @Query methods: Room supports return values of type Publisher, Flowable, and Observable
  • @Insert, @Update, and @Delete methods: Room 2.1.0 and higher supports return values of type Completable, Single<T>, and Maybe<T>

要使用此功能,請在 app 的 build.gradle 文件中包含最新版本的 rxjava2:

dependencies {
    implementation 'androidx.room:room-rxjava2:2.1.0-alpha02'
}

以下代碼示範如何使用這些返回類型:

@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 and RxJava
 

Direct cursor access

如果需要直接存取返回的行,則可以從查詢中返回 Cursor 物件,如以下代碼段所示:

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

警告:
非常不建議使用 Cursor API,因為它不能保證行是否存在或行包含的值。
 

Query multiple tables

某些查詢可能需要訪問多個表來計算結果。Room 允許編寫任何查詢,因此也可以查詢多個資料表。
此外,如果響應是可觀察的數據類型(如 Flowable 或 LiveData),則會監視查詢中引用的所有資料表以檢查是否無效。
以下代碼段顯示如何查詢不同的資料表:

@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。

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