Definir e consultar relações aninhadas

Às vezes, pode ser necessário consultar um conjunto de três ou mais tabelas relacionadas entre si. Nesse caso, é preciso definir relações aninhadas entre as tabelas.

Suponha que, no exemplo do app de streaming de música, você queira consultar todos os usuários, todas as playlists de cada usuário e todas as músicas em cada playlist de cada usuário. Usuários têm uma relação de um para muitos com playlists, e listas de reprodução têm uma relação de muitos para muitos com músicas. O exemplo de código abaixo mostra as classes que representam essas entidades e também a tabela de referência cruzada para a relação de muitos para muitos entre playlist e músicas:

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

Primeiro, modele a relação entre duas tabelas no seu conjunto como você faria normalmente, com uma classe de dados e a @Relation anotação. O exemplo abaixo mostra uma classe PlaylistWithSongs que modela uma relação de muitos para muitos entre a classe de entidade Playlist e a classe de entidade Song:

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

Depois de definir uma classe de dados que representa essa relação, crie outra classe que modele a relação entre outra tabela do conjunto e a classe de relação primária, "aninhando" a relação existente com a nova. O exemplo abaixo mostra uma classe UserWithPlaylistsAndSongs que modela uma relação de um para muitos entre a classe de entidade User e a PlaylistWithSongs classe de relação:

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

A classe UserWithPlaylistsAndSongs modela indiretamente as relações entre as três classes de entidade: User, Playlist e Song. Isso é ilustrado na figura 1.

UserWithPlaylistsAndSongs modela a relação entre as classes User (usuário) e
  PlaylistWithSongs, que, por sua vez, modela a relação entre playlist
  e músicas.
Figura 1. Diagrama de classes de relação no exemplo do app de streaming de música.

Se o conjunto tiver mais tabelas, crie uma classe para modelar a relação entre cada tabela restante e a classe de relação anterior. Esse processo cria uma cadeia de relações aninhadas entre todas as tabelas que você quer consultar.

Por fim, adicione uma função à classe de objeto de acesso a dados (DAO, na sigla em inglês) para expor a função de consulta que seu app precisa. Essa função exige que o Room execute várias consultas. Portanto, adicione a anotação @Transaction para garantir que toda a operação seja executada atomicamente:

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