譯自Define relationships between objects | Android Developers
此前一直困惑於 Room 如何實現多對多關係的問題,奈何當時全網搜不到相關的內容,於時遷移到 Room 的計劃便被擱置了。直到我最近從 Google Codelab 的 Android Room with a View 中的連結跳轉到了官方文件,看到了官方對於多對多關係的示例程式碼,我又去搜尋了一下這個問題,中文站內已經有幾篇相關的問答了,但是掘金似乎還沒有相關的文章提到(可能是我搜尋的方式不對吧),於是我決定將這段文件翻譯分享出來。以下是正文。
由於 SQLite 資料庫是一種關係型資料庫,你可以指定物件間的關係。然而儘管絕大部分的物件關係對映庫允許實體物件相互引用,但是 Room 明確禁止這種做法。想要了解這個決定的技術性原因,參見理解為什麼 Room 不允許物件引用。
定義一對多關係
儘管你不能使用直接關係,但是 Room 允許你定義外來鍵關聯實體類。
例如有一個實體類 User
,另一個實體類 Book
,你可以通過 @ForeignKey
宣告,定義 Book
與 User
的關係。
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
}
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
public class Book {
@PrimaryKey public int bookId;
public String title;
@ColumnInfo(name = "user_id") public int userId;
}
複製程式碼
由於 Book
的 0 或多個例項可以通過外來鍵 user_id
關聯到 User
的多個例項上,因此上面的程式碼模擬了 Book
與 User
之間的一對多關係。
外來鍵非常的強大,甚至允許你指定引用的實體更新後執行的操作。例如,通過在 @ForeignKey
的宣告中加入 onDelete = CASCADE
,你可以設定 SQLite 在一個 User
的例項被刪除時,刪除該例項關聯的所有書。
★ 注意: SQLite 通過一系列的 刪除 和 替換 操作處理 @Insert(onConflict = REPLACE) 而非單一的 更新 操作。這種替換衝突值得方法可能會影響你的外來鍵約束。多於更多細節,參見 SQLite 文件 的 ON_CONFLICT 部分。
建立巢狀物件
有時,你希望在資料庫邏輯中將實體或資料物件表達為一個整體,即使該物件包含多個欄位。在這些情況下,你可以使用 @Embedded
註解來表示表中要分解到子成員變數的物件。 然後,你可以像查詢其他單個列一樣查詢嵌入的成員變數。
例如,您的User類可以包含Address型別的欄位,該欄位表示名為street,city,state和postCode的欄位的組合。 要將組合列分別儲存在表中,在User類中使用@Embedded註解修飾的Address欄位,如以下程式碼段所示:
public class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = "post_code") public int postCode;
}
@Entity
public class User {
@PrimaryKey public int id;
public String firstName;
@Embedded public Address address;
}
複製程式碼
這張表表示包含具有以下名稱的列: id
, firstName
, street
, state
, city
和 post_code
的 User
物件。
★ 注意: 巢狀欄位也可以包含其他巢狀欄位。
如果實體具有多個相同型別的嵌入欄位,則可以通過設定 prefix
屬性使每個列保持唯一。然後,Room將提供的值新增到嵌入物件中每個列名稱的開頭。
定義多對多關係
PS:終於到重點了,嘿嘿?
你經常要在關聯式資料庫中建模另一種關係:兩個實體之間的多對多關係,其中每個實體可以關聯到另一個實體的0或多個例項。例如,考慮一個音樂串流應用程式,使用者可以將他們喜歡的歌曲組成播放列表。每個播放列表可以具有任意數量的歌曲,並且每首歌曲可以包括在任意數量的播放列表中。
要模擬此關係,你需要建立三個物件:
- 播放列表的實體類。
- 歌曲的實體類。
- 一箇中間類,用於儲存每個播放列表中有哪些歌曲的資訊。
你可以將實體類定義為獨立部分:
@Entity
public class Playlist {
@PrimaryKey public int id;
public String name;
public String description;
}
@Entity
public class Song {
@PrimaryKey public int id;
public String songName;
public String artistName;
}
複製程式碼
然後,將中間類定義為包含對 Song
和 Playlist
的外來鍵引用的實體:
@Entity(tableName = "playlist_song_join",
primaryKeys = { "playlistId", "songId" },
foreignKeys = {
@ForeignKey(entity = Playlist.class,
parentColumns = "id",
childColumns = "playlistId"),
@ForeignKey(entity = Song.class,
parentColumns = "id",
childColumns = "songId")
})
public class PlaylistSongJoin {
public int playlistId;
public int songId;
}
複製程式碼
這會生成一個多對多關係模型,允許你使用 DAO 按播放列表按歌曲和歌曲查詢播放列表:
@Dao
public interface PlaylistSongJoinDao {
@Insert
void insert(PlaylistSongJoin playlistSongJoin);
@Query("SELECT * FROM playlist " +
"INNER JOIN playlist_song_join " +
"ON playlist.id=playlist_song_join.playlistId " +
"WHERE playlist_song_join.songId=:songId")
List<Playlist> getPlaylistsForSong(final int songId);
@Query("SELECT * FROM song " +
"INNER JOIN playlist_song_join " +
"ON song.id=playlist_song_join.songId " +
"WHERE playlist_song_join.playlistId=:playlistId")
List<Song> getSongsForPlaylist(final int playlistId);
}
複製程式碼