LoginSignup
13
9

More than 3 years have passed since last update.

Roomで多対多のEntityを取得する方法

Posted at

はじめに

みなさんRoom使っていますか?
SQLiteの面倒な部分をいい感じにやってくれるいいヤツです。

多対多な関係にあるEntityの取得方法に苦戦にしていたところ
最高の解決方法があったのでシェアします。

多対多(Many-to-Many)の関係

多対多の関係とはなんぞや?知っている方はスキップで大丈夫です。

簡単に言えば、音楽のプレイリストと楽曲の関係と同じです。

例えば、「新海誠」というプレイリストに

  • 「前前前世/RADWIMPS」
  • 「愛にできることはまだあるかい/RADWIMPS」
  • 「One more time, One more chance/山崎まさよし」
  • 「Rain/秦基博」

という複数の楽曲が登録されているとします。

一方で、「RADWIMPSマイベスト」というプレイリストには

  • 「前前前世/RADWIMPS」
  • 「愛にできることはまだあるかい/RADWIMPS」
  • 「おしゃかしゃま/RADWIMPS」

という複数の楽曲が登録されているとします。

「新海誠」と「RADWIMPSマイベスト」には共通して

  • 「前前前世/RADWIMPS」
  • 「愛にできることはまだあるかい/RADWIMPS」

という楽曲があることが分かります。

このように、一つのプレイリストに対して複数の楽曲が紐付いていて、一つの楽曲に対しても複数のプレイリストに紐付いているような関係を多対多と呼びます。

本題

Roomを使ってこの関係を表現しようとすると、

@Entity
data class PlayList(
    @PrimaryKey val id: Long,
    val name: String
)

@Entity
data class Song(
    @PrimaryKey val id: Long,
    val name: String
)

@Entity(
    primaryKeys = ["play_list_id", "song_id"],
    foreignKeys = [
        ForeignKey(
            entity = PlayList::class,
            parentColumns = arrayOf("id"),
            childColumns = arrayOf("play_list_id")
        ),
        ForeignKey(
            entity = Song::class,
            parentColumns = arrayOf("id"),
            childColumns = arrayOf("song_id")
        )
    ]
)
data class PlayListAndSongXRef(
    @ColumnInfo(name = "play_list_id")
    val playListId: Long,
    @ColumnInfo(name = "song_id")
    val SongId: Long,
)

のようにプレイリストと楽曲の関係を表す中間テーブルを用意して実現すると思います。
(Songクラスには簡単のためにアーティスト名は省略)

では、特定のプレイリストとそのプレイリストに紐づく楽曲一覧を取得したいとしましょう。
以下のようなEntityとして返ってくることを期待しています

data class PlayListAndSongs(
    val playList: PlayList,
    val songs: List<Song>
)

これをDao経由で一発で取得する方法が分からず、頭を悩ませていましたが
Room2.2-alpha01から@Junctionというアノテーションが追加され、

試しに使ってみたらいともたやすく解決できてしまいました。

@Junctionアノテーションは、今回のような中間テーブル経由で紐づく複数のデータを取得する際に利用することができます。

使い方は簡単です。

class PlayListAndSongs {
    @Embedded
    lateinit var playList: PlayList // ①

    @Relation(
        entity = Song::class, // ②
        parentColumn = "id", // ③
        entityColumn = "id", // ④
        associateBy = Junction(
            value = PlayListAndSongXRef::class, // ⑤
            parentColumn = "play_list_id", // ⑥ 
            entityColumn = "song_id" // ⑦
        )
    )
    lateinit var songs: List<Song>
}

①:@Embeddedアノテーションを取得する親になるフィールドに付与します。
②:@Relationアノテーションを複数の子になるフィールドに付与して、entityに子のクラスを指定します。
parentColumn@Embeddedを付与したクラス(PlayList)と中間テーブル(PlayListAndSongXRef)を紐付けるためのPlayList側のキー
entityColumnSongと中間テーブルを紐付けるためのSong側のキー

※③、④のカラム名が同じでややこしくなってしまった・・・。

⑤: associateByにはvalueに中間テーブルのクラス、
parentColumn@Embeddedを付与したクラス(PlayList)と中間テーブル(PlayListAndSongXRef)を紐付けるための中間テーブル側のキー
entityColumnSongと中間テーブルを紐付けるための中間テーブル側のキー

を指定します。

あとはDaoを

@Dao
interface PlayListAndSongsDao {
    @Query("""
        SELECT *
        FROM PlayList
        WHERE name = :name
        LIMIT 1
    """)
    fun getPlayListAndSongsByName(name: String): PlayListAndSongs
}

のように定義して、

val playListAndSongs = dao.getPlayListAndSongsByName("新海誠")

とすれば「新海誠」のプレイリストとそれに紐づく複数の楽曲データがリストで取得できます。

@Junction すげー!

2019/8/20段階ではalphaな機能なのでプロダクションで使うには検討が必要ですが、他対他のデータ取得がとても簡単になるので、
ぜひ導入していきたいですね。
自分は個人開発アプリでガシガシ使っていこうと思います。

13
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
9