ネストされたリレーションを定義してクエリを実行する

場合によっては、互いに関連している 3 つ以上のテーブルをクエリする必要があります。その場合、テーブル間にネストされたリレーションを定義します。

音楽ストリーミング アプリの例で、全ユーザー、各ユーザーの全プレイリスト、各ユーザーの各プレイリストの全曲をクエリするとします。ユーザーにはプレイリストとの 1 対多のリレーションがあり、プレイリストには曲との多対多のリレーションがあります。次のコード例は、これらのエンティティを表すクラスと、プレイリストと曲との多対多のリレーションを表す相互参照テーブルを示しています。

@Entity
data class User(
    @PrimaryKey val userId: Long,
    val name: String,
    val age: Int
)

@Entity
data class Playlist(
    @PrimaryKey val playlistId: Long,
    val userCreatorId: 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
)

まず、通常どおり、セットの中で 2 つのテーブル間のリレーションを、データクラスと @Relation アノテーションを使用してモデル化します。次の例は、Playlist エンティティ クラスと Song エンティティ クラスとの間の多対多のリレーションをモデル化する PlaylistWithSongs クラスを示しています。

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

このリレーションを表すデータクラスを定義したら、セット内の別のテーブルと最初のリレーション クラスをモデル化する別のデータクラスを作成し、新しいリレーション内に既存のリレーションを「ネスト」します。次の例は、User エンティティ クラスと PlaylistWithSongs リレーション クラスとの間の 1 対多のリレーションをモデル化する UserWithPlaylistsAndSongs クラスを示しています。

data class UserWithPlaylistsAndSongs(
    @Embedded val user: User,
    @Relation(
        entity = Playlist::class,
        parentColumns = ["userId"],
        entityColumns = ["userCreatorId"]
    )
    val playlists: List<PlaylistWithSongs>
)

UserWithPlaylistsAndSongs クラスは、3 つのエンティティ クラス UserPlaylistSong のリレーションを間接的にモデル化します。このことを図 1 に示します。

UserWithPlaylistsAndSongs は User と PlaylistWithSongs のリレーションをモデル化し、PlaylistWithSongs は Playlist と Song のリレーションをモデル化します。
図 1. 音楽ストリーミング アプリの例でのリレーション クラスの図。

セット内に他にもテーブルがある場合は、残りの各テーブル間のリレーションをモデル化するクラスと、以前のリレーション クラスを作成します。このプロセスでは、クエリを実行するすべてのテーブル間にネストされたリレーションのチェーンが作成されます。

最後に、データアクセス オブジェクト(DAO)クラスに関数を追加して、アプリに必要なクエリ関数を公開します。この関数では Room に複数のクエリを実行させる必要があるため、@Transaction アノテーションを追加して、操作全体がアトミックに実行されるようにします。

@Transaction
@Query("SELECT * FROM User")
suspend fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>