定义和查询多对多关系

两个实体之间的多对多关系是指其中一个父实体的每个实例对应零个或多个子实体的实例,反之亦然。

在音乐流媒体应用示例中,考虑用户定义的播放列表中的歌曲。每个播放列表可以包含许多歌曲,而每首歌曲都可以是许多不同播放列表的一部分。因此,Playlist 实体和 Song 实体之间存在多对多关系。

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

  1. 定义关系:建立实体和关联实体(交叉引用表)来表示多对多关系。
  2. 查询实体:确定你希望如何查询相关实体,并创建数据类来表示预期的输出。

定义关系

要定义多对多关系,首先为你两个实体中的每一个创建一个类。多对多关系与其他关系类型不同,因为子实体中通常没有对父实体的引用。取而代之的是,创建第三个类来表示两个实体之间的关联实体,或者称为交叉引用表。交叉引用表必须包含表中表示的多对多关系中每个实体的外键列。在此示例中,交叉引用表中的每一行都对应一个 Playlist 实例和一个 Song 实例的配对,其中引用的歌曲包含在引用的播放列表中。

Kotlin

@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"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val songId: Long
)

Java

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public String playlistName;
}

@Entity
public class Song {
    @PrimaryKey public long songId;
    public String songName;
    public String artist;
}

@Entity(primaryKeys = {"playlistId", "songId"})
public class PlaylistSongCrossRef {
    public long playlistId;
    public long songId;
}

查询实体

下一步取决于你希望如何查询这些相关实体。

  • 如果你想查询播放列表以及每个播放列表对应的歌曲列表,则创建一个新的数据类,其中包含一个 Playlist 对象以及播放列表包含的所有 Song 对象的列表。
  • 如果你想查询歌曲以及每首歌曲对应的播放列表列表,则创建一个新的数据类,其中包含一个 Song 对象以及包含该歌曲的所有 Playlist 对象的列表。

无论哪种情况,都通过在这些类中的 @Relation 注解中使用 associateBy 属性来建模实体之间的关系,以标识提供 Playlist 实体和 Song 实体之间关系的交叉引用实体。

Kotlin

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

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

Java

public class PlaylistWithSongs {
    @Embedded public Playlist playlist;
    @Relation(
         parentColumn = "playlistId",
         entityColumn = "songId",
         associateBy = @Junction(PlaylistSongCrossref.class)
    )
    public List<Song> songs;
}

public class SongWithPlaylists {
    @Embedded public Song song;
    @Relation(
         parentColumn = "songId",
         entityColumn = "playlistId",
         associateBy = @Junction(PlaylistSongCrossref.class)
    )
    public List<Playlist> playlists;
}

最后,向 DAO 类添加一个方法,以暴露你的应用所需的查询函数。

  • getPlaylistsWithSongs:此方法查询数据库并返回所有生成的 PlaylistWithSongs 对象。
  • getSongsWithPlaylists:此方法查询数据库并返回所有生成的 SongWithPlaylists 对象。

这些方法都需要 Room 运行两次查询,因此在这两个方法上都添加 @Transaction 注解,以便整个操作原子化执行。

Kotlin

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

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

Java

@Transaction
@Query("SELECT * FROM Playlist")
public List<PlaylistWithSongs> getPlaylistsWithSongs();

@Transaction
@Query("SELECT * FROM Song")
public List<SongWithPlaylists> getSongsWithPlaylists();