はじめに
みなさん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
側のキー
④entityColumn
:Song
と中間テーブルを紐付けるためのSong
側のキー
※③、④のカラム名が同じでややこしくなってしまった・・・。
⑤: associateBy
にはvalue
に中間テーブルのクラス、
⑥ parentColumn
:@Embedded
を付与したクラス(PlayList
)と中間テーブル(PlayListAndSongXRef
)を紐付けるための中間テーブル側のキー
⑦entityColumn
:Song
と中間テーブルを紐付けるための中間テーブル側のキー
を指定します。
あとは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な機能なのでプロダクションで使うには検討が必要ですが、他対他のデータ取得がとても簡単になるので、
ぜひ導入していきたいですね。
自分は個人開発アプリでガシガシ使っていこうと思います。