다대다 관계 정의 및 쿼리

두 항목 간의 다대다 관계 는 상위 항목의 각 인스턴스가 0개 이상의 하위 항목 인스턴스에 상응하며, 그 반대의 경우도 마찬가지입니다.

음악 스트리밍 앱의 예에서 사용자가 정한 재생목록의 노래를 생각해 보세요. 각 재생목록에는 노래가 여러 곡 포함될 수 있으며 각 노래는 여러 재생목록에 속할 수 있습니다. 따라서 Playlist 항목과 Song 항목 간에는 다대다 관계가 성립됩니다.

다음 단계에 따라 데이터베이스에서 다대다 관계를 정의하고 쿼리합니다.

  1. **관계 정의**: 다대다 관계를 나타내기 위해 항목과 연결 항목 또는 교차 참조 테이블을 설정합니다.
  2. 항목 쿼리: 관련 항목을 쿼리하는 방법을 결정하고 의도한 출력을 나타내는 데이터 클래스를 만듭니다.

관계 정의

다대다 관계를 정의하려면 먼저 두 항목 각각의 클래스를 만듭니다. 다대다 관계는 일반적으로 하위 항목에 상위 항목에 대한 참조가 없기 때문에 다른 관계 유형과 구별됩니다. 대신 세 번째 클래스를 만들어 두 항목 간의 연결 항목(또는 상호 참조 테이블)을 나타냅니다. 교차 참조 테이블에는 테이블에 표시된 다대다 관계에 있는 각 항목의 기본 키 열이 있어야 합니다. 이 예에서 상호 참조 테이블의 각 행은 Playlist 인스턴스와 Song 인스턴스의 쌍이며, 여기서 참조된 재생목록은 참조된 노래를 포함합니다.

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

항목 쿼리

다음 단계는 이러한 관련 항목을 쿼리하는 방법에 따라 다릅니다.

  • 재생목록 및 각 재생목록에 상응하는 노래 목록을 쿼리하려면 단일 Playlist 객체 및 재생목록에 포함된 Song 객체 목록을 포함하는 새 데이터 클래스를 만듭니다.
  • 노래 및 각 노래에 상응하는 재생목록 목록을 쿼리하려면 단일 Song 객체 및 노래를 포함하는 Playlist 객체 목록을 포함하는 새 데이터 클래스를 만듭니다.

어느 경우든 이러한 각 클래스의 @Relation 주석에서 associateBy 속성을 사용하여 항목 간의 관계를 모델링함으로써 Playlist 항목과 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>
)

마지막으로 데이터 액세스 객체 (DAO) 클래스에 함수를 추가하여 앱에 필요한 쿼리 함수를 노출합니다.

getPlaylistsWithSongs
데이터베이스를 쿼리하고 결과 PlaylistWithSongs 객체를 모두 반환합니다.
getSongsWithPlaylists
데이터베이스를 쿼리하고 결과 SongWithPlaylists 객체를 모두 반환합니다.

각 함수를 사용하려면 Room에서 두 개의 쿼리를 실행해야 합니다. 두 함수 모두에 @Transaction 주석을 추가하여 작업이 원자적으로 실행되도록 합니다.

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

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

복합 키

복합 키를 사용하여 관계를 정의하는 경우 @Relation 주석의 parentColumnsentityColumns에 여러 열을 지정합니다.

Junction에서 열을 지정해야 하는 경우 Junction 주석의 parentColumnsentityColumns도 사용합니다.

다음 예에서 Playlist에는 playlistIdcreatorId로 구성된 복합 기본 키가 있습니다. 상호 참조 테이블 PlaylistSongCrossRef에는 재생목록을 참조하는 이러한 열도 포함됩니다.

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