設計一個關係型數據庫很重要的一部分是將數據拆分紅具備相關關係的數據表,而後將數據以符合這種關係的邏輯方式整合到一塊兒。從 Room 2.2 的穩定版開始,咱們可利用一個 @Relation 註解來支持表之間全部可能出現的關係: 一對1、一對多和多對多。html
假設咱們生活在一個每一個人只能擁有一隻狗,且每隻狗只能有一個主人的 「悲慘世界」 中,這就是一對一關係。若是要以關係型數據庫的方式來反應它的話,咱們能夠建立兩張表: Dog 表和 Owner 表,其中 Dog 表經過 owner id 來引用 Owner 表中的數據,或者 Owner 表經過 dog id 來引用 Dog 表中的數據。android
@Entity data class Dog( @PrimaryKey val dogId: Long, val dogOwnerId: Long, val name: String, val cuteness: Int, val barkVolume: Int, val breed: String ) @Entity data class Owner(@PrimaryKey val ownerId: Long, val name: String)
假設咱們想在一個列表中展現全部的狗和它們的主人,咱們須要建立一個 DogAndOwner 類:sql
data class DogAndOwner( val owner: Owner, val dog: Dog )
爲了在 SQLite 中進行查詢,咱們須要 1) 運行兩個查詢: 一個獲取全部的主人數據,一個獲取全部的狗狗數據,2) 根據 owner id 來進行數據的關係映射。數據庫
SELECT * FROM Owner SELECT * FROM Dog WHERE dogOwnerId IN (ownerId1, ownerId2, …)
要在 Room 中獲取一個 List<DogAndOwner>
,咱們不須要本身去實現上面說的查詢和映射,只須要使用 @Relation
註解。google
在咱們的示例中,因爲 Dog
有了 owner 的信息,咱們給 dog 變量增長 @Relation
註解,指定父級 (這裏對應 Owner
) 上的 ownerId 列對應 dogOwnerId
:spa
data class DogAndOwner( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entityColumn = "dogOwnerId" ) val dog: Dog )
如今咱們的 Dao 類可被簡化成:設計
@Transaction @Query("SELECT * FROM Owner") fun getDogsAndOwners(): List<DogAndOwner>
注意: 因爲 Room 會默默的幫咱們運行兩個查詢請求,所以須要增長 @Transaction 註解來確保這個行爲是原子性的。3d
再假設,一個主人能夠養多隻狗狗,如今上面的關係就變成了一對多關係。咱們以前定義的數據庫 schema 並不須要改變,仍然使用一樣的表結構,由於在 「多」 這一方的表中已經有了關聯鍵。code
如今,要展現狗和主人的列表,咱們須要建立一個新的類來進行建模:sqlite
data class OwnerWithDogs( val owner: Owner, val dogs: List<Dog> )
爲了不運行兩個獨立的查詢,咱們能夠在 Dog 和 Owner 中定義一對多的關係,一樣,仍是在 List<Dog> 前增長 @Relation
註解。
data class OwnerWithDogs( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entityColumn = "dogOwnerId" ) val dogs: List<Dog> )
如今,Dao 類又變成了這樣:
@Transaction @Query("SELECT * FROM Owner") fun getDogsAndOwners(): List<OwnerWithDogs>
如今,繼續假設咱們生活在一個完美的世界中,一我的能夠擁有多隻狗,每隻狗能夠擁有多個主人。要對這個關係進行映射,以前的 Dog 和 Owner 表是不夠的。因爲一隻狗狗能夠有多個主人,咱們須要在同一個 dog id 上可以匹配多個不一樣的 owner id。因爲 dogId 是 Dog 表的主鍵,咱們不能直接在 Dog 表中添加一樣 id 的多條數據。爲了解決這個問題,咱們須要建立一個 associative 表 (也被稱爲鏈接表),這個表來存儲 (dogId, ownerId) 的數據對。
@Entity(primaryKeys = ["dogId", "ownerId"]) data class DogOwnerCrossRef( val dogId: Long, val ownerId: Long )
若是如今咱們想要獲取到全部的狗狗和主人的數據,也就是 List<OwnerWithDogs>
,僅須要編寫兩個 SQLite 查詢,一個獲取到全部的主人數據,另外一個獲取 Dog 和 DogOwnerCrossRef 表的鏈接數據。
SELECT * FROM Owner SELECT Dog.dogId AS dogId, Dog.dogOwnerId AS dogOwnerId, Dog.name AS name, _junction.ownerId FROM DogOwnerCrossRef AS _junction INNER JOIN Dog ON (_junction.dogId = Dog.dogId) WHERE _junction.ownerId IN (ownerId1, ownerId2, …)
要經過 Room 來實現這個功能,咱們須要更新 OwnerWithDogs 數據類,並告訴 Room 要使用 DogOwnerCrossRef 這個鏈接表來獲取 Dogs 數據。咱們經過使用 Junction 引用這張表。
data class OwnerWithDogs( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entityColumn = "dogId", associateBy = Junction(DogOwnerCrossRef::class) ) val dogs: List<Dog> )
在咱們的 Dao 中,咱們須要從 Owners 中選擇並返回正確的數據類:
@Transaction @Query("SELECT * FROM Owner") fun getOwnersWithDogs(): List<OwnerWithDogs>
當使用 @Relation 註解時,Room 會默認從所修飾的屬性類型推斷出要使用的數據庫實體。例如,到目前爲止咱們用 @Relation 修飾了 Dog (或者是 List<Dog>),Room 就會知道如何去對該類進行建模,以及知道要查詢的究竟是哪一行數據。
若是您想讓該查詢返回一個不一樣的類,好比 Pup 這樣不是一個數據庫實體可是包含了一些字段的對象。咱們能夠在 @Relation 註解中指定要使用的數據庫實體:
data class Pup( val name: String, val cuteness: Int = 11 ) data class OwnerWithPups( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entity = Dog::class, entityColumn = "dogOwnerId" ) val dogs: List<Pup> )
若是咱們只想從數據庫實體中返回特定的列,您須要經過在 @Relation 中的 projection 屬性中定義要返回哪些列。例如,假如咱們只想獲取 OwnerWithDogs 數據類中全部狗的名字,因爲咱們須要用到 List<String>,Room 不能推斷出這些字符串是對應於狗的品種呢仍是狗的名字,所以咱們須要在 projection 屬性中指名。
data class OwnerWithDogs( @Embedded val owner: Owner, @Relation( parentColumn = "ownerId", entity = Dog::class, entityColumn = "dogOwnerId", projection = ["name"] ) val dogNames: List<String> )
若是您想在 dogOwnerId 和 ownerId 中定義更嚴格的關係,而無論您所建立的是什麼,您能夠經過在字段中使用 ForeignKey 來作到。記住,SQLite 中的外鍵 會建立索引,而且會在更新或者刪除表中數據時作級聯操做。所以您要根據實際狀況來判斷是否使用外鍵功能。
無論您是要使用一對一,一對多仍是多對多關係,Room 都會爲您提供 @Relation 註解來解決問題。您能夠在咱們的 Android Dev Summit ’19 的一個 演講 中瞭解有關 Room 2.2 的更多新功能。
點擊這裏 進一步瞭解 Room。