定義及查詢多對多關係

兩個實體之間的「多對多關係」是指父系實體的每個例項分別對應到子實體的零個或更多例項,反之亦然。

在音樂串流應用程式範例中,使用者定義的播放清單中有許多歌曲。每個播放清單可包含許多歌曲,而每首歌曲也可能位於不同播放清單中。因此,PlaylistSong 實體之間是多對多關係。

請按照下列步驟,在資料庫中定義及查詢多對多關係:

  1. 定義關係:建立實體和關聯實體 (或交叉參照資料表),代表多對多關係。
  2. 查詢實體:決定如何查詢相關實體,並建立資料類別來表示預期輸出內容。

定義關係

如要定義多對多關係,請先為這兩個實體分別建立類別。由於子實體通常不含上層實體的參照,因此多對多關係與其他關係類型不同。請改為建立第三個類別來代表這兩個實體之間的關聯實體或「交叉參照資料表」。交叉參照資料表必須包含主鍵資料欄,該欄代表資料表內多對多關係中的每個實體。在本例中,交叉比對資料表中的每一個資料列都會對應至一對 Playlist 例項和 Song 例項,其中參照的播放清單會包含參照的歌曲。

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "songId"], indices = [Index("playlistId", "songId")])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

查詢實體

下一個步驟視您想要查詢相關實體的方式而定。

  • 如要查詢「播放清單」和每個播放清單的對應「歌曲」清單,請建立新的資料類別,當中包含單一 Playlist 物件以及該播放清單收錄的 Song 物件清單。
  • 如要查詢「歌曲」和每首歌曲的對應「播放清單」清單,請建立新的資料類別,當中包含單一 Song 物件以及收錄該歌曲的 Playlist 物件清單。

無論是哪一種情況,您都可以在這些類別的 @Relation 註解中使用 associateBy 屬性,建立實體之間的關係模型,以找出建立 Playlist 實體與 Song 實體兩者之間關係的交叉參照實體。

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
        parentColumns = ["playlistId"],
        entityColumns = ["songId"],
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)

data class SongWithPlaylists(
    @Embedded val song: Song,
    @Relation(
        parentColumns = ["songId"],
        entityColumns = ["playlistId"],
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val playlists: List<Playlist>
)

最後,在資料存取物件 (DAO) 類別中新增函式,以顯示應用程式所需的查詢函式。

getPlaylistsWithSongs
查詢資料庫,並傳回所有產生的 PlaylistWithSongs 物件。
getSongsWithPlaylists
查詢資料庫,並傳回所有產生的 SongWithPlaylists 物件。

每個函式都需要 Room 執行兩項查詢。在兩個函式中加入 @Transaction 註解,確保作業以不可分割的形式執行。

@Transaction
@Query("SELECT * FROM Playlist")
suspend fun getPlaylistsWithSongs(): List<PlaylistWithSongs>

@Transaction
@Query("SELECT * FROM Song")
suspend fun getSongsWithPlaylists(): List<SongWithPlaylists>

複合鍵

如果使用複合鍵定義關係,請在 @Relation 註解的 parentColumnsentityColumns 中指定多個資料欄。

如要在 Junction 中指定資料欄,請在 Junction 註解中一併使用 parentColumnsentityColumns

在以下範例中,Playlist 具有由 playlistIdcreatorId 組成的複合主鍵。交叉參照表 PlaylistSongCrossRef 也包含這些資料欄,可參照播放清單。

@Entity(primaryKeys = ["playlistId", "creatorId"])
data class Playlist(
    val playlistId: Long,
    val creatorId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "creatorId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val creatorId: Long,
    val songId: Long
)

data class PlaylistWithSongs(
    @Embedded val playlist: Playlist,
    @Relation(
        parentColumns = ["playlistId", "creatorId"],
        entityColumns = ["songId"],
        associateBy = Junction(PlaylistSongCrossRef::class)
    )
    val songs: List<Song>
)