Как определять и запрашивать вложенные отношения

Иногда вам может понадобиться запросить набор из трех или более таблиц, связанных друг с другом. В этом случае вы определяете вложенные отношения между таблицами.

Предположим, что в примере с приложением для потоковой передачи музыки вы хотите запросить всех пользователей, все плейлисты для каждого пользователя и все песни в каждом плейлисте для каждого пользователя. У пользователей с плейлистами отношения "один ко многим", а у плейлистов с песнями – "многие ко многим". В следующем примере кода показаны классы, представляющие эти объекты, а также таблица перекрестных ссылок для отношения "многие ко многим" между плейлистами и песнями:

@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
)

Сначала смоделируйте связь между двумя таблицами из набора, как вы обычно это делаете, используя класс данных и аннотацию @Relation. В следующем примере показан класс PlaylistWithSongs, который моделирует связь "многие ко многим" между классами объектов Playlist и Song:

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

Определив класс данных, представляющий эту связь, создайте ещё один класс данных, который моделирует связь между другой таблицей из вашего набора и первым классом связи, "вложив" существующую связь в новую. В примере ниже показан класс UserWithPlaylistsAndSongs, который моделирует связь "один ко многим" между классом объектов User и классом связей PlaylistWithSongs:

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

Класс UserWithPlaylistsAndSongs косвенно моделирует отношения между всеми тремя классами объектов: User, Playlist и Song. Это показано на рисунке 1.

UserWithPlaylistsAndSongs моделирует связь между User и PlaylistWithSongs, которая, в свою очередь, моделирует связь между Playlist и Song.
Рисунок 1. Диаграмма классов отношений в примере приложения для стриминга музыки

Если в наборе больше таблиц, создайте класс для моделирования связи между каждой оставшейся таблицей и предыдущим классом связи. В результате этого процесса создается цепочка вложенных связей между всеми таблицами, к которым вы хотите отправить запрос.

Наконец, добавьте в класс объекта доступа к данным (DAO) функцию, которая будет предоставлять доступ к функции запроса, необходимой вашему приложению. Эта функция требует, чтобы Room выполнял несколько запросов, поэтому добавьте аннотацию @Transaction, чтобы вся операция выполнялась атомарно:

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