2 つのエンティティ間の多対多のリレーションとは、親エンティティの各インスタンスが子エンティティの 0 個以上のインスタンスに対応するリレーション、あるいはその逆のリレーションです。
音楽ストリーミング アプリの例で、ユーザー定義のプレイリストの曲について考えてみましょう。各プレイリストには多数の曲を含めることができ、それぞれの曲は多数のプレイリストに含められます。したがって、Playlist エンティティと Song エンティティの間には多対多のリレーションがあります。
データベースで多対多のリレーションを定義してクエリを実行する手順は次のとおりです。
- リレーションを定義する: 多対多のリレーションを表すエンティティと連関エンティティ(相互参照テーブル)を確立します。
- エンティティをクエリする: 関連エンティティをクエリする方法を決定し、目的の出力を表すデータクラスを作成します。
リレーションシップを定義する
多対多のリレーションを定義するには、まず 2 つのエンティティにそれぞれクラスを作成します。多対多のリレーションは、通常、子エンティティに親エンティティへの参照がないため、他のタイプのリレーションとは区別されます。代わりに、2 つのエンティティ間の連関エンティティ(相互参照テーブルとも呼ばれます)を表す 3 つ目のクラスを作成します。相互参照テーブルには、テーブルで表現される多対多のリレーションに含まれる各エンティティからの主キーの列が必要です。この例では、相互参照テーブルの各行は、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 )
エンティティをクエリする
次のステップは、これらの連関エンティティのクエリ方法によって異なります。
- プレイリストと各プレイリストの対応する曲のリストをクエリする場合は、1 つの
Playlistオブジェクトとプレイリストに含まれるSongオブジェクトのリストを含む新しいデータクラスを作成します。 - 曲と各曲に対応するプレイリストのリストをクエリする場合は、1 つの
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 に 2 つのクエリを実行させる必要があります。両方の関数に @Transaction アノテーションを追加して、操作がアトミックに実行されるようにします。
@Transaction @Query("SELECT * FROM Playlist") suspend fun getPlaylistsWithSongs(): List<PlaylistWithSongs> @Transaction @Query("SELECT * FROM Song") suspend fun getSongsWithPlaylists(): List<SongWithPlaylists>
複合キー
複合キーを使用してリレーションシップを定義する場合は、@Relation アノテーションの parentColumns と entityColumns で複数の列を指定します。
Junction で列を指定する必要がある場合は、Junction アノテーションで parentColumns と entityColumns も使用します。
次の例では、Playlist に playlistId と creatorId で構成される複合主キーがあります。相互参照テーブル 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> )