オブジェクト間の関係タイプを選択する

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

関係の種類

Room は、次の関係タイプをサポートしています。

  • 1 対 1: 単一のエンティティが別の単一のエンティティに関連付けられている関係を表します。
  • 1 対多: 1 つのエンティティが別のタイプの複数のエンティティに関連付けられる関係を表します。
  • 多対多: 1 つのタイプの複数のエンティティが別のタイプの複数のエンティティに関連付けられるリレーションを表します。通常、結合テーブルが必要です。
  • ネストされた関係(埋め込みオブジェクトを使用): エンティティに別のエンティティがフィールドとして含まれ、このネストされたエンティティにさらに他のエンティティが含まれる関係を表します。これには @Embedded アノテーションを使用します。

2 つのアプローチから選択する

Room では、エンティティ間のリレーションを定義してクエリする方法が 2 つあります。次のいずれかを使用できます。

  • 埋め込みオブジェクトを含む中間データクラス。
  • マルチマップを戻り値の型とするリレーショナル クエリ メソッド。

中間データクラスを使用する特別な理由がない場合は、マルチマップの戻り値の型アプローチを使用することをおすすめします。このアプローチについて詳しくは、マルチマップを返すをご覧ください。

中間データクラス アプローチでは、複雑な SQL クエリを作成せずに済みますが、必要なデータクラスを追加することで、コードが複雑になる可能性もあります。つまり、マルチマップの戻り値の型アプローチでは SQL クエリで行う処理が多くなり、中間データクラス アプローチではコードで行う処理が多くなります。

中間データクラスのアプローチを使用する

中間データクラス アプローチでは、Room エンティティ間のリレーションをモデル化するデータクラスを定義します。このデータクラスは、あるエンティティのインスタンスと別のエンティティのインスタンスのペアリングを埋め込みオブジェクトとして保持します。その後、このデータクラスのインスタンスがクエリメソッドによって返され、アプリで使用できます。

たとえば、特定の本を借りた図書館利用者を表す UserBook データクラスを定義し、データベースから UserBook インスタンスのリストを取得するクエリメソッドを定義できます。

Kotlin

@Dao
interface UserBookDao {
    @Query(
        "SELECT user.name AS userName, book.name AS bookName " +
        "FROM user, book " +
        "WHERE user.id = book.user_id"
    )
    fun loadUserAndBookNames(): LiveData<List<UserBook>>
}

data class UserBook(val userName: String?, val bookName: String?)

Java

@Dao
public interface UserBookDao {
   @Query("SELECT user.name AS userName, book.name AS bookName " +
          "FROM user, book " +
          "WHERE user.id = book.user_id")
   public LiveData<List<UserBook>> loadUserAndBookNames();
}

public class UserBook {
    public String userName;
    public String bookName;
}

マルチマップの戻り値の型のアプローチを使用する

マルチマップの戻り値の型アプローチでは、追加のデータクラスを定義する必要はありません。代わりに、必要なマップ構造に基づいてメソッドにマルチマップの戻り値の型を定義し、エンティティ間のリレーションを SQL クエリで直接定義します。

たとえば、次のクエリメソッドは、特定の本を借りた図書館利用者を表す User インスタンスと Book インスタンスのマッピングを返します。

Kotlin

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
fun loadUserAndBookNames(): Map<User, List<Book>>

Java

@Query(
    "SELECT * FROM user" +
    "JOIN book ON user.id = book.user_id"
)
public Map<User, List<Book>> loadUserAndBookNames();

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

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

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

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 プロパティを設定することで、各列を一意にすることができます。指定した値が、埋め込みオブジェクトの各列名の先頭に自動的に追加されます。

参考情報

Room 内のエンティティ間のリレーションを定義する方法について詳しくは、次の参考情報をご覧ください。

動画

ブログ