다대다 관계 정의 및 쿼리

두 항목 간의 다대다 관계 는 상위 항목의 각 인스턴스가 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 주석의 parentColumns 및 entityColumns에 여러 열을 지정합니다.

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

다음 예에서 Playlist에는 playlistId 및 creatorId로 구성된 복합 기본 키가 있습니다. 상호 참조 테이블 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>
)