多対多のリレーションを定義してクエリを実行する

2 つのエンティティ間の多対多のリレーションとは、親エンティティの各インスタンスが子エンティティの 0 個以上のインスタンスに対応するリレーション、あるいはその逆のリレーションです。

音楽ストリーミング アプリの例で、ユーザー定義のプレイリストの歌について考えてみましょう。各プレイリストには多数の曲を含めることができ、それぞれの曲は多数の異なるプレイリストに含められます。したがって、Playlist エンティティと Song エンティティの間には多対多のリレーションがあります。

データベースで多対多のリレーションを定義してクエリを実行する手順は次のとおりです。

  1. リレーションを定義する: エンティティと関連エンティティ(相互参照テーブル)を確立して、多対多のリレーションを表します。
  2. エンティティをクエリする: 関連するエンティティをクエリする方法を確認し、目的の出力を表すデータクラスを作成します。

関係を定義する

多対多のリレーションを定義するには、まず 2 つのエンティティにそれぞれクラスを作成します。多対多のリレーションは、通常、子エンティティに親エンティティへの参照がないため、他のタイプのリレーションとは区別されます。代わりに、2 つのエンティティ間の連関エンティティ(相互参照テーブル)を表す 3 つ目のクラスを作成します。相互参照テーブルには、テーブルで表現される多対多のリレーションに含まれる各エンティティからの主キーの列が必要です。この例では、相互参照テーブルの各行は、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;
}

エンティティをクエリする

次のステップは、これらの連関エンティティのクエリ方法によって異なります。

  • プレイリストと各プレイリストの対応する曲のリストをクエリする場合は、1 つの Playlist オブジェクトとプレイリストに含まれる Song オブジェクトのリストを含む新しいデータクラスを作成します。
  • とそれに対応するプレイリストのリストをクエリする場合は、1 つの 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 に 2 つのクエリを実行させる必要があるため、両方のメソッドに @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();