nashcft's blog

時々何か書く。

Jetpack Room: One-to-many relationship の結合クエリで並び順を指定する方法のメモ

Jetpack Room で one-to-many relationship の tableを、 one 側を parent とし、 many 側を list property としてまとめて一つの結果として取得したいとき、 many 側の list の並び順を指定する方法についてのメモ、というか以下の issue のまとめ直し:

以後の説明で用いる実装例は Android Developers の以下のページに基にしているため、記事内では entity や DAO の定義などを省略している:

developer.android.com

また、以下に動作デモ用の repository を作成している:

github.com

方法 1. アプリ側で並び替える

クエリによる並び替えの制御を諦めて、取得した後にアプリ側のロジックで並べ替える。なんとも敗北感のある実装ではあるが、取得するデータのサイズがそんなに大きくならないと見込めるのであれば、場合によっては実装コスト的に検討の余地があるかもしれない。

dao.getUsersWithPlaylists().map { userWithPlaylists ->
    userWithPlaylists.copy(
        playlists = userWithPlaylists.playlist.sortedBy { it.playlistName }
    )
}

方法 2. Database view を用いて並び順を指定する

Embedded object と @Relation annotation を使って結合を表現する場合そのままではアプリ側で順序の指定をすることができないが、 Room 2.1.0 以降では SQLite の view という機能を使うことで view を介した順序の指定が可能である。 Database view については以下を参照。

developer.android.com

まず ORDER BY を指定したクエリを使った view を定義する:

@DatabaseView("SELECT * FROM playlist ORDER BY playlistName ASC")
data class SortedPlaylist(
    // SELECT の指定が "*" なので Playlist の property と一致させる
)

この database view を embedded object の @Relationentity に指定する。このとき property playList の型は List<Playlist> のままで問題ない:

data class UserWithPlaylists(
    @Embedded val user: User,
    @Relation(
        parentColumn = "userId",
        entityColumn = "userCreatorId",
        entity = SortedPlaylist::class,
    )
    val playlists: List<Playlist>,
)

方法 3. Multimap として取得する

以下のページで紹介されているように、 one 側を key, many 側を list value とした multi-map でクエリの返却値を表現することで、 SQLORDER BY を記述して任意の並び替えを指定することができる。 Room 2.4.0 から利用できる。

developer.android.com

@Query(
    value = """
        SELECT *
        FROM user
        JOIN playlist ON user.userId = playlist.userCreatorId
        ORDER BY user.userId ASC, playlist.playlistName ASC
    """
)
suspend fun getUsersWithPlaylists(): Map<User, List<Playlist>>

Map として返却する都合上 2つの table 間の結合しか表現できないという制約があるが、一方でこれが採用できる場面ではあれこれと class を作成しなくて良いという点はメリットとなる。 One 側を複数取得する場合の順序の保証については、生成されたコードの中身を見た感じ (JVM では) LinkedHashMap を使っていたので大丈夫なのではないか。個人的な所感としては Map の形で欲しいことなんてある? というのがあるが、アプリ用に別途 model を定義していて詰め替えが発生するような設計ならまあ良いかという感じもする。

おわりに

Database view を使うのが手法を統一できて良いかなーという所感。