選擇物件之間的關係類型

由於 SQLite 是關聯資料庫,因此您可以定義實體之間的關係。儘管大多數的物件關聯對應程式庫允許實體物件互相參照,但 Room 明確禁止這種做法。如要瞭解這項決策背後的技術原因,請參閱「瞭解 Room 不允許物件參照的原因」。

關係類型

Room 支援下列關係類型:

  • 一對一:代表單一實體與另一個單一實體相關的關係。
  • 一對多:代表單一實體可與其他類型的多個實體建立關聯的關係。
  • 多對多:代表多個某類型實體可與多個其他類型實體建立關聯的關係。這通常需要聯結資料表。
  • 使用內嵌物件的巢狀關係:代表實體包含另一個實體做為屬性的關係,而這個巢狀實體可以進一步包含其他實體。這項操作會使用 @Embedded 註解。

選擇兩種做法之一

在 Room 中,您可以透過兩種方式定義及查詢實體之間的關係。你可以使用下列任一方法:

  • 含有內嵌物件的中繼資料類別,或
  • 具有多重對應傳回類型的關聯查詢函式。

如果沒有需要採用中繼資料類別的特定理由,建議您採用多重對應傳回類型的方法。如要進一步瞭解這種方法,請參閱「傳回多重對應」一文。

採用中繼資料類別的方法可讓您避免編寫複雜的 SQL 查詢,但也可能因為需要額外的資料類別,導致程式碼更加複雜。簡單來說,採用多重對應傳回類型的方法需要透過 SQL 查詢完成更多作業,而採用中繼資料類別的方法則需透過程式碼完成更多作業。

使用中繼資料類別方法

如要採用中繼資料類別的方式,您必須定義資料類別,以建立 Room 實體之間的關係模型。這個資料類別會保留其中一個實體的例項與另一個實體的例項之間的配對連線,做為內嵌物件。這樣一來,您的查詢函式就能傳回這個資料類別的例項,以便用於應用程式。

舉例來說,您可以定義 UserBook 資料類別來代表曾借出特定圖書的圖書館使用者,並定義查詢函式,從資料庫擷取 UserBook 例項清單:

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

data class UserBook(val userName: String, val bookName: String)

使用多重對應傳回類型方法

如要採用多重對應傳回類型的方式,您不需要定義任何其他資料類別,而是根據您想要的對應結構,為函式定義多重對應傳回類型,並且直接在 SQL 查詢中定義實體之間的關係。

舉例來說,以下查詢函式會傳回 UserBook 例項的對應關係,以代表曾借出特定圖書的圖書館使用者:

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

使用多重對應傳回型別時,您也可以查詢不涉及其他實體的一對一關係。下列查詢函式會傳回 User 的對應關係,以及他們使用 @MapColumn 註解借出的書籍數量:

@Query(
    """
    SELECT user.*, COUNT(book.id) AS book_count
    FROM user LEFT JOIN book ON user.id = book.user_id
    GROUP BY user.id
    """
)
suspend fun loadUserAndBookCount(): Map<User, @MapColumn(columnName = "book_count") Int>

建立內嵌物件

有時候,即使物件含有多個屬性,您仍會希望在資料庫邏輯中將實體或資料物件表示為一個凝聚的整體。如果是這種情況,請使用 @Embedded 註解,將物件細分為資料表中的子屬性。這樣一來,您就可以使用查詢其他資料欄的相同方式,查詢內嵌的屬性。

舉例來說,User 類別可以包含 Address 屬性,該屬性代表 streetcitystatepostCode 屬性的組合。如要在資料表中分別儲存組成的資料欄,請使用 @Embedded 註解 User 類別中的 Address 屬性。下列程式碼片段顯示這項設定:

data class Address(
    val street: String?,
    val state: String?,
    val city: String?,
    @ColumnInfo(name = "post_code") val postCode: Int
)

@Entity
data class User(
    @PrimaryKey val id: Int,
    val firstName: String,
    @Embedded val address: Address?
)

這樣一來,代表 User 物件的資料表就會包含使用以下名稱的資料欄:idfirstNamestreetstatecitypost_code

如果實體含有多個相同類型的內嵌屬性,您可以設定 prefix 屬性,讓每個資料欄使用不重複的名稱。接著,Room 會將提供的值加到內嵌物件中每個資料欄名稱的開頭。