Cómo definir y consultar relaciones de varios a varios

En las relaciones de varios a varios entre dos entidades, cada instancia de la entidad principal corresponde a cero o más instancias de la entidad secundaria y viceversa.

En el ejemplo de la app de transmisión de música, imagina las canciones en las playlists definidas por el usuario. Cada playlist puede incluir muchas canciones y cada canción puede pertenecer a muchas playlists. Por lo tanto, existe una relación de varios a varios entre las entidades Playlist y Song.

Sigue estos pasos para definir y consultar relaciones de varios a varios en tu base de datos:

  1. Define la relación: Establece las entidades y la entidad asociativa, o tabla de referencias cruzadas, para representar la relación de varios a varios.
  2. Consulta las entidades: Determina cómo quieres consultar las entidades relacionadas y crea clases de datos para representar el resultado deseado.

Define la relación

Para definir una relación de varios a varios, primero crea una clase para cada una de las dos entidades. Las relaciones de varios a varios son diferentes de otros tipos de relación porque, por lo general, no hay una referencia a la entidad principal en la entidad secundaria. Entonces, crea una tercera clase para representar una entidad asociativa (o tabla de referencias cruzadas) entre las dos entidades. La tabla de referencias cruzadas debe tener columnas para la clave primaria de cada entidad contemplada en la relación de varios a varios que se representa en la tabla. En este ejemplo, cada fila de la tabla de referencias cruzadas corresponde a una vinculación de una instancia Playlist y una instancia Song donde la playlist a la que se hace referencia incluye la canción a la que se hace referencia.

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

Consulta las entidades

El siguiente paso depende de cómo quieras consultar las entidades relacionadas.

  • Si quieres consultar playlists y un listado de las canciones correspondientes por cada playlist, crea una clase de datos nueva con un objeto Playlist único y un listado de los objetos Song que incluye la playlist.
  • Si quieres consultar canciones y un listado de las playlists correspondientes por cada canción, crea una clase de datos nueva con un objeto Song único y un listado de los objetos Playlist que incluyen la canción.

En ambos casos, modela la relación entre las entidades mediante la associateBy propiedad en la @Relation anotación de cada una de estas clases para identificar la entidad de la referencia cruzada que proporciona la relación entre la entidad Playlist y la entidad Song.

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

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

Por último, agrega una función a la clase de objeto de acceso a datos (DAO) para exponer la función de consulta que necesita la app.

getPlaylistsWithSongs
Consulta la base de datos y muestra todos los objetos PlaylistWithSongs resultantes.
getSongsWithPlaylists
Consulta la base de datos y muestra todos los objetos SongWithPlaylists resultantes.

Cada función requiere que Room ejecute dos consultas. Agrega la @Transaction anotación a ambas funciones para asegurarte de que la operación se ejecute automáticamente.

@Transaction
@Query("SELECT * FROM Playlist")
suspend fun getPlaylistsWithSongs(): List<PlaylistWithSongs>

@Transaction
@Query("SELECT * FROM Song")
suspend fun getSongsWithPlaylists(): List<SongWithPlaylists>

Claves compuestas

Si defines la relación con claves compuestas, especifica varias columnas en parentColumns y entityColumns de la anotación @Relation.

Si necesitas especificar columnas en Junction, usa parentColumns y entityColumns en la anotación Junction también.

En el siguiente ejemplo, Playlist tiene una clave primaria compuesta por playlistId y creatorId. La tabla de referencias cruzadas PlaylistSongCrossRef también incluye estas columnas para hacer referencia a la playlist.

@Entity(primaryKeys = ["playlistId", "creatorId"])
data class Playlist(
    val playlistId: Long,
    val creatorId: Long,
    val playlistName: String
)

@Entity
data class Song(
    @PrimaryKey val songId: Long,
    val songName: String,
    val artist: String
)

@Entity(primaryKeys = ["playlistId", "creatorId", "songId"])
data class PlaylistSongCrossRef(
    val playlistId: Long,
    val creatorId: Long,
    val songId: Long
)

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