Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

オブジェクト間のリレーションを定義する

SQLite はリレーショナル データベースであるため、エンティティ間のリレーションを指定できます。ほとんどのオブジェクト リレーショナル マッピング ライブラリにおいて、エンティティ オブジェクト間の相互参照が可能ですが、Room では明示的に禁止されています。この決定の背景にある技術的な理由については、Room がオブジェクト参照をサポートしない理由を理解するをご覧ください。

埋め込みオブジェクトを作成する

オブジェクトに複数のフィールドが含まれている場合でも、データベース ロジック内では、エンティティやデータ オブジェクトを 1 つのまとまりとして表現したい場合があります。このような場合、@Embedded アノテーションを使用することで、テーブル内のサブフィールドに分解されるオブジェクトを表現できます。このような埋め込みフィールドは、通常の列と同様にクエリを行うことができます。

たとえば、User クラスに Address 型のフィールドが含まれているとします。このフィールドは streetcitystatepostCode という名前のフィールドで構成されています。構成後の列をテーブル内に個別に格納するには、@Embedded アノテーションを付けた User クラスに Address フィールドを含めます。次のコード スニペットをご覧ください。

Kotlin

    data class Address(
        val street: String?,
        val state: String?,
        val city: String?,
        @ColumnInfo(name = "post_code") val postCode: Int
    )

    @Entity
    data class User(
        @PrimaryKey val id: Int,
        val firstName: String?,
        @Embedded val address: Address?
    )
    

Java

    public class Address {
        public String street;
        public String state;
        public String city;

        @ColumnInfo(name = "post_code") public int postCode;
    }

    @Entity
    public class User {
        @PrimaryKey public int id;

        public String firstName;

        @Embedded public Address address;
    }
    

User オブジェクトを表すテーブルには、idfirstNamestreetstatecitypost_code という名前の列が含まれるようになります。

1 つのエンティティに同じ型の埋め込みフィールドが複数ある場合、prefix プロパティを設定することで、各列を一意にすることができます。指定した値が、埋め込みオブジェクトの各列名の先頭に自動的に追加されます。

1 対 1 のリレーションを定義する

2 つのエンティティ間の 1 対 1 のリレーションとは、親エンティティの各インスタンスが子エンティティの 1 つのインスタンスに対応するリレーション、あるいはその逆のリレーションです。

たとえば、音楽ストリーミング アプリで、ユーザーが自分の所有する曲のライブラリを持っている場合を考えてみましょう。各ユーザーはライブラリを 1 つだけ持ち、各ライブラリは 1 人のユーザーに対応しています。したがって、User エンティティと Library エンティティの間には 1 対 1 のリレーションがあります。

まず、2 つのエンティティにそれぞれクラスを作成します。一方のエンティティには、他方のエンティティの主キーへの参照である変数を含める必要があります。

Kotlin

    @Entity
    data class User(
        @PrimaryKey val userId: Long,
        val name: String,
        val age: Int
    )

    @Entity
    data class Library(
        @PrimaryKey val libraryId: Long,
        val userOwnerId: Long
    )
    

Java

    @Entity
    public class User {
        @PrimaryKey public long userId;
        public String name;
        public int age;
    }

    @Entity
    public class Library {
        @PrimaryKey public long libraryId;
        public long userOwnerId;
    }
    

ユーザーと対応するライブラリのリストをクエリするには、最初に 2 つのエンティティ間の 1 対 1 のリレーションをモデル化する必要があります。このために、親エンティティのインスタンスと子エンティティの対応するインスタンスを保持する新しいデータクラスを作成します。@Relation アノテーションを子エンティティのインスタンスに追加します。このとき、parentColumn を親エンティティの主キー列の名前に設定し、entityColumn を親エンティティの主キーを参照する子エンティティの列の名前に設定します。

Kotlin

    data class UserAndLibrary(
        @Embedded val user: User,
        @Relation(
             parentColumn = "userId",
             entityColumn = "userOwnerId"
        )
        val library: Library
    )
    

Java

    public class UserAndLibrary {
        @Embedded public User user;
        @Relation(
             parentColumn = "userId",
             entityColumn = "userOwnerId"
        )
        public Library library;
    }
    

最後に、親エンティティと子エンティティをペアにするデータクラスのすべてのインスタンスを返すメソッドを DAO クラスに追加します。このメソッドでは Room に 2 つのクエリを実行させる必要があるため、@Transaction アノテーションを追加して、操作全体がアトミックに実行されるようにします。

Kotlin

    @Transaction
    @Query("SELECT * FROM User")
    fun getUsersAndLibraries(): List<UserAndLibrary>
    

Java

    @Transaction
    @Query("SELECT * FROM User")
    public List<UserAndLibrary> getUsersAndLibraries();
    

1 対多のリレーションを定義する

2 つのエンティティ間の 1 対多のリレーションとは、親エンティティの各インスタンスが子エンティティの 0 個以上のインスタンスに対応し、子エンティティの各インスタンスは親エンティティの 1 つのインスタンスにだけ対応するリレーションです。

音楽ストリーミング アプリで、ユーザーがプレイリストに曲を整理できる機能があるとします。各ユーザーが作成できるプレイリストの数に制限はありませんが、各プレイリストの作成者は 1 人だけです。したがって、User エンティティと Playlist エンティティの間には 1 対多のリレーションがあります。

まず、2 つのエンティティにそれぞれクラスを作成します。前の例と同様に、子エンティティには、親エンティティの主キーへの参照である変数を含める必要があります。

Kotlin

    @Entity
    data class User(
        @PrimaryKey val userId: Long,
        val name: String,
        val age: Int
    )

    @Entity
    data class Playlist(
        @PrimaryKey val playlistId: Long,
        val userCreatorId: Long,
        val playlistName: String
    )
    

Java

    @Entity
    public class User {
        @PrimaryKey public long userId;
        public String name;
        public int age;
    }

    @Entity
    public class Playlist {
        @PrimaryKey public long playlistId;
        public long userCreatorId;
        public String playlistName;
    }
    

ユーザーと対応するプレイリストのリストをクエリするには、最初に 2 つのエンティティ間の 1 対多のリレーションをモデル化する必要があります。このために、親エンティティのインスタンスと、対応する子エンティティのインスタンスのリストを保持する新しいデータクラスを作成します。@Relation アノテーションを子エンティティのインスタンスに追加します。このとき、parentColumn を親エンティティの主キー列の名前に設定し、entityColumn を親エンティティの主キーを参照する子エンティティの列の名前に設定します。

Kotlin

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

Java

    public class UserWithPlaylists {
        @Embedded public User user;
        @Relation(
             parentColumn = "userId",
             entityColumn = "userCreatorId"
        )
        public List<Playlist> playlists;
    }
    

最後に、親エンティティと子エンティティをペアにするデータクラスのすべてのインスタンスを返すメソッドを DAO クラスに追加します。このメソッドでは Room に 2 つのクエリを実行させる必要があるため、@Transaction アノテーションを追加して、操作全体がアトミックに実行されるようにします。

Kotlin

    @Transaction
    @Query("SELECT * FROM User")
    fun getUsersWithPlaylists(): List<UserWithPlaylists>
    

Java

    @Transaction
    @Query("SELECT * FROM User")
    public List<UserWithPlaylists> getUsersWithPlaylists();
    

多対多のリレーションを定義する

2 つのエンティティ間の多対多のリレーションとは、親エンティティの各インスタンスが子エンティティの 0 個以上のインスタンスに対応するリレーション、あるいはその逆のリレーションです。

もう一度、音楽ストリーミング アプリの例で、ユーザー定義のプレイリストを考えてみましょう。各プレイリストには多数の曲を含めることができ、それぞれの曲は多数の異なるプレイリストに含められます。したがって、Playlist エンティティと Song エンティティの間には多対多のリレーションがあります。

まず、2 つのエンティティにそれぞれクラスを作成します。多対多のリレーションは、通常、子エンティティに親エンティティへの参照がないため、他のタイプのリレーションとは区別されます。代わりに、2 つのエンティティ間の連関エンティティ(相互参照テーブル)を表す 3 つ目のクラスを作成します。相互参照テーブルには、テーブルで表現される多対多のリレーションに含まれる各エンティティからの主キーの列が必要です。この例では、相互参照テーブルの各行は、Playlist インスタンスと Song インスタンスのペアに対応し、参照先の曲が参照先のプレイリストに含まれます。

Kotlin

    @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"])
    data class PlaylistSongCrossRef(
        val playlistId: Long,
        val songId: Long
    )
    

Java

    @Entity
    public class Playlist {
        @PrimaryKey public long playlistId;
        public String playlistName;
    }

    @Entity
    public class Song {
        @PrimaryKey public long songId;
        public String songName;
        public String artist;
    }

    @Entity(primaryKeys = {"playlistId", "songId"})
    public class PlaylistSongCrossRef {
        public long playlistId;
        public long songId;
    }
    

次のステップは、これらの連関エンティティのクエリ方法によって異なります。

  • プレイリストと各プレイリストの対応する曲のリストをクエリする場合は、1 つの Playlist オブジェクトとプレイリストに含まれる Song オブジェクトのリストを含む新しいデータクラスを作成します。
  • とそれに対応するプレイリストのリストをクエリする場合は、1 つの Song オブジェクトと、曲が含まれる Playlist オブジェクトのリストを含む、新しいデータクラスを作成します。

いずれの場合も、各クラスの @Relation アノテーションの associateBy プロパティでエンティティ間のリレーションをモデル化して、Playlist エンティティと Song エンティティのリレーションを定める相互参照エンティティを指定します。

Kotlin

    data class PlaylistWithSongs(
        @Embedded val playlist: Playlist,
        @Relation(
             parentColumn = "playlistId",
             entityColumn = "songId",
             associateBy = @Junction(PlaylistSongCrossRef::class)
        )
        val songs: List<Song>
    )

    data class SongWithPlaylists(
        @Embedded val song: Song,
        @Relation(
             parentColumn = "songId",
             entityColumn = "playlistId",
             associateBy = @Junction(PlaylistSongCrossRef::class)
        )
        val playlists: List<Playlist>
    )
    

Java

    public class PlaylistWithSongs {
        @Embedded public Playlist playlist;
        @Relation(
             parentColumn = "playlistId",
             entityColumn = "songId",
             associateBy = @Junction(PlaylistSongCrossref.class)
        )
        public List<Song> songs;
    }

    public class SongWithPlaylists {
        @Embedded public Song song;
        @Relation(
             parentColumn = "songId",
             entityColumn = "playlistId",
             associateBy = @Junction(PlaylistSongCrossref.class)
        )
        public List<Playlist> playlists;
    }
    

最後に、DAO クラスにメソッドを追加して、アプリに必要なクエリ機能を公開します。

  • getPlaylistsWithSongs: このメソッドは、データベースにクエリを実行して、結果の PlaylistWithSongs オブジェクトをすべて返します。
  • getSongsWithPlaylists: このメソッドは、データベースにクエリを実行して、結果の SongWithPlaylists オブジェクトをすべて返します。

これらのメソッドでは Room に 2 つのクエリを実行させる必要があるため、両方のメソッドに @Transaction アノテーションを追加して、操作全体がアトミックに実行されるようにします。

Kotlin

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

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

Java

    @Transaction
    @Query("SELECT * FROM Playlist")
    public List<PlaylistWithSongs> getPlaylistsWithSongs();

    @Transaction
    @Query("SELECT * FROM Song")
    public List<SongWithPlaylists> getSongsWithPlaylists();
    

ネストされたリレーションを定義する

場合によっては、互いに関連している 3 つ以上のテーブルをクエリする必要があります。その場合、テーブル間にネストされたリレーションを定義します。

音楽ストリーミング アプリの例で、全ユーザー、各ユーザーの全プレイリスト、各ユーザーの各プレイリストの全曲をクエリするとします。ユーザーにはプレイリストとの 1 対多のリレーションがあり、プレイリストには曲との多対多のリレーションがあります。次のコード例は、これらのエンティティを表すクラスと、プレイリストと曲との多対多のリレーションを表す相互参照テーブルを示しています。

Kotlin

    @Entity
    data class User(
        @PrimaryKey val userId: Long,
        val name: String,
        val age: Int
    )

    @Entity
    data class Playlist(
        @PrimaryKey val playlistId: Long,
        val userCreatorId: Long,
        val playlistName: String
    )

    @Entity
    data class Song(
        @PrimaryKey val songId: Long,
        val songName: String,
        val artist: String
    )

    @Entity(primaryKeys = ["playlistId", "songId"])
    data class PlaylistSongCrossRef(
        val playlistId: Long,
        val songId: Long
    )
    

Java

    @Entity
    public class User {
        @PrimaryKey public long userId;
        public String name;
        public int age;
    }

    @Entity
    public class Playlist {
        @PrimaryKey public long playlistId;
        public long userCreatorId;
        public String playlistName;
    }
    @Entity
    public class Song {
        @PrimaryKey public long songId;
        public String songName;
        public String artist;
    }

    @Entity(primaryKeys = {"playlistId", "songId"})
    public class PlaylistSongCrossRef {
        public long playlistId;
        public long songId;
    }
    

まず、通常通り、セットの中で 2 つのテーブル間のリレーションを、データクラスと @Relation アノテーションでモデル化します。次の例は、Playlist エンティティ クラスと Song エンティティ クラスとの間の多対多のリレーションをモデル化する PlaylistWithSongs クラスを示しています。

Kotlin

    data class PlaylistWithSongs(
        @Embedded val playlist: Playlist,
        @Relation(
             parentColumn = "playlistId",
             entityColumn = "songId",
             associateBy = @Junction(PlaylistSongCrossRef::class)
        )
        val songs: List<Song>
    )
    

Java

    public class PlaylistWithSongs {
        @Embedded public Playlist playlist;
        @Relation(
             parentColumn = "playlistId",
             entityColumn = "songId",
             associateBy = @Junction(PlaylistSongCrossRef.class)
        )
        public List<Song> songs;
    }
    

このリレーションを表すデータクラスを定義したら、セット内の別のテーブルと最初のリレーション クラスをモデル化する別のデータクラスを作成し、新しいリレーション内に既存のリレーションを「ネスト」します。次の例は、User エンティティ クラスと PlaylistWithSongs リレーション クラスとの間の 1 対多のリレーションをモデル化する UserWithPlaylistsAndSongs クラスを示しています。

Kotlin

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

Java

    public class UserWithPlaylistsAndSongs {
        @Embedded public User user;
        @Relation(
            entity = Playlist.class,
            parentColumn = "userId",
            entityColumn = "userCreatorId"
        )
        public List<PlaylistWithSongs> playlists;
    }
    

UserWithPlaylistsAndSongs クラスは、3 つのエンティティ クラス UserPlaylistSong のリレーションを間接的にモデル化します。このことを図 1 に示します。

UserWithPlaylistsAndSongs は User と PlaylistWithSongs のリレーションをモデル化し、PlaylistWithSongs は Playlist と Song のリレーションをモデル化します。

図 1. 音楽ストリーミング アプリの例でのリレーション クラスの図

セット内に他にもテーブルがある場合は、残りの各テーブル間のリレーションをモデル化するクラスと、以前のテーブルすべての間のリレーションをモデル化するリレーション クラスを作成します。これにより、クエリを実行するすべてのテーブル間にネストされたリレーションのチェーンが作成されます。

最後に、DAO クラスにメソッドを追加して、アプリに必要なクエリ機能を公開します。このメソッドでは Room に複数のクエリを実行させる必要があるため、@Transaction アノテーションを追加して、操作全体がアトミックに実行されるようにします。

Kotlin

    @Transaction
    @Query("SELECT * FROM User")
    fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>
    

Java

    @Transaction
    @Query("SELECT * FROM User")
    public List<UserWithPlaylistsAndSongs> getUsersWithPlaylistsAndSongs();
    

参考情報

Room 内のエンティティ間のリレーションを定義する方法について詳しくは、以下のリソースをご覧ください。

サンプル

動画

ブログ