定义和查询多对多关系

两个实体之间的 多对多关系是指这样一种关系:父实体的每个实例对应于子实体的零个或多个实例,反之亦然。

在音乐在线影音应用示例中,考虑用户定义的播放列表中的歌曲。每个播放列表都可以包含多首歌曲,每首歌曲都可以包含在多个播放列表中。因此,Playlist 实体和 Song 实体之间存在多对多关系。

请按照以下步骤在数据库中定义和查询多对多关系:

  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>
)