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

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

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

Kotlin

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

Java

@Entity
public class User {
    @PrimaryKey public long userId;
    public String name;
    public int age;
}

@Entity
public class Playlist {
    @PrimaryKey public long playlistId;
    public long userCreatorId;
    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;
}

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

Kotlin

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

Java

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

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

Kotlin

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

Java

public class UserWithPlaylistsAndSongs {
    @Embedded public User user;
    @Relation(
        entity = Playlist.class,
        parentColumn = "userId",
        entityColumn = "userCreatorId"
    )
    public List<PlaylistWithSongs> playlists;
}

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

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

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

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

Kotlin

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

Java

@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();