Menentukan hubungan antar-objek

Karena SQLite merupakan database relasional, Anda dapat menentukan hubungan antara entity. Namun, meskipun sebagian besar library pemetaan terkait objek memungkinkan objek entity saling mereferensikan satu sama lain, Room secara eksplisit melarang hal ini. Untuk mempelajari alasan teknis di balik keputusan ini, lihat Memahami alasan Room tidak mengizinkan referensi objek.

Dua kemungkinan pendekatan

Di Room, ada dua cara untuk menentukan dan membuat kueri hubungan antar-entity: menggunakan class data perantara dengan objek yang disematkan atau metode kueri relasional dengan jenis nilai yang ditampilkan multimap.

Class data menengah

Dalam pendekatan class data perantara, Anda menentukan class data yang memodelkan hubungan antara entity Room. Class data ini menyimpan penyambungan antara instance satu entity dan instance entity lain sebagai objek tersemat. Metode kueri Anda kemudian dapat menampilkan instance class data ini untuk digunakan dalam aplikasi.

Misalnya, Anda dapat menentukan class data UserBook untuk mewakili pengguna library dengan buku tertentu yang diperiksa, dan menentukan metode kueri untuk mengambil daftar instance UserBook dari database:

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;
}

Jenis nilai yang ditampilkan multimap

Dalam pendekatan jenis nilai yang ditampilkan multimap, Anda tidak perlu menentukan class data tambahan apa pun. Sebagai gantinya, Anda harus menentukan jenis nilai yang ditampilkan multimap untuk metode Anda berdasarkan struktur peta yang diinginkan, dan tentukan hubungan antara entity Anda secara langsung dalam kueri SQL.

Misalnya, metode kueri berikut menampilkan pemetaan instance User dan Book untuk mewakili pengguna library dengan buku tertentu yang diperiksa:

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();

Memilih pendekatan

Room mendukung kedua pendekatan ini, sehingga Anda dapat menggunakan pendekatan mana pun yang paling sesuai untuk aplikasi Anda. Bagian ini membahas beberapa alasan mengapa Anda mungkin lebih memilih salah satunya.

Pendekatan class data perantara memungkinkan Anda untuk tidak menulis kueri SQL yang kompleks, tetapi juga dapat mengakibatkan peningkatan kerumitan kode karena class data tambahan yang dibutuhkan. Singkatnya, pendekatan jenis nilai yang ditampilkan multimap memerlukan kueri SQL Anda untuk melakukan lebih banyak pekerjaan, dan pendekatan class data perantara memerlukan kode Anda untuk melakukan lebih banyak pekerjaan.

Jika Anda tidak memiliki alasan khusus untuk menggunakan class data perantara, sebaiknya gunakan pendekatan jenis nilai yang ditampilkan multimap. Untuk mempelajari pendekatan ini lebih lanjut, lihat Menampilkan multimap.

Bagian lainnya dalam panduan ini menunjukkan cara menetapkan hubungan menggunakan pendekatan class data perantara.

Membuat objek yang disematkan

Terkadang, Anda perlu menyatakan entity atau objek data sebagai satu kesatuan yang kohesif dalam logika database meskipun objek berisi beberapa kolom. Dalam situasi ini, Anda dapat menggunakan anotasi @Embedded untuk merepresentasikan objek yang ingin didekompresi ke subkolomnya dalam tabel. Anda kemudian dapat meminta kolom tersemat tersebut seperti saat meminta kolom individual lainnya.

Misalnya, class User dapat menyertakan kolom dari jenis Address yang merepresentasikan susunan kolom bernama street, city, state, dan postCode. Untuk menyimpan kolom tersusun secara terpisah dalam tabel, sertakan kolom Address di class User yang dianotasikan dengan @Embedded, seperti yang ditunjukan dalam cuplikan kode berikut:

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;
}

Tabel yang merepresentasikan objek User kemudian akan berisi kolom dengan nama berikut: id, firstName, street, state, city, dan post_code.

Jika suatu entity memiliki beberapa kolom tersemat yang jenisnya sama, Anda dapat mengatur setiap kolom agar tetap unik dengan menetapkan properti prefix . Room kemudian menambahkan nilai yang diberikan ke awal setiap nama kolom dalam objek yang tersemat.

Menentukan hubungan one-to-one

Hubungan one-to-one antara dua entity adalah hubungan saat setiap instance parent entity berkaitan dengan tepat satu instance entity turunan, dan hal sebaliknya juga berlaku.

Misalnya, pertimbangkan aplikasi streaming musik tempat pengguna memiliki library lagu yang mereka miliki. Setiap pengguna hanya memiliki satu library, dan setiap library berkaitan dengan tepat satu pengguna. Oleh karena itu, ada hubungan one-to-one antara entity User dan entity Library.

Untuk menentukan hubungan one-to-one, pertama-tama buat class untuk kedua entitas Anda. Salah satu entity harus menyertakan variabel yang merupakan referensi ke kunci utama entity lainnya.

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;
}

Untuk membuat kueri daftar pengguna dan koleksi terkait, Anda harus terlebih dahulu memodelkan hubungan one-to-one antara dua entity. Untuk melakukannya, buat class data baru tempat setiap instance menyimpan instance parent entity dan instance entity turunan yang sesuai. Tambahkan anotasi @Relation ke instance entity turunan, dengan parentColumn ditetapkan ke nama kolom kunci utama dari parent entity dan entityColumn ditetapkan ke nama kolom entity turunan yang mereferensikan kunci utama parent entity.

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;
}

Terakhir, tambahkan metode ke class DAO yang menampilkan semua instance class data yang menghubungkan parent entity dan entity turunan. Metode ini memerlukan Room untuk menjalankan dua kueri, jadi tambahkan anotasi @Transaction ke metode ini sehingga seluruh operasi dijalankan secara atomik.

Kotlin

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

Java

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

Menentukan hubungan one-to-many

Hubungan one-to-many antara dua entity adalah hubungan saat setiap instance parent entity tidak berkaitan atau berkaitan dengan beberapa instance entity turunan, tetapi setiap instance entity turunan hanya berkaitan dengan tepat satu instance parent entity.

Dalam contoh aplikasi streaming musik, misalnya pengguna memiliki kemampuan untuk mengatur lagu mereka ke dalam playlist. Setiap pengguna dapat membuat playlist sebanyak yang diinginkan, tetapi setiap playlist dibuat oleh satu pengguna saja. Oleh karena itu, ada hubungan one-to-many antara entity User dan entity Playlist.

Untuk menentukan hubungan one-to-many, buat class untuk dua entity terlebih dahulu. Seperti dalam hubungan one-to-one, entity turunan harus menyertakan variabel yang merupakan referensi ke kunci utama parent entity.

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;
}

Untuk membuat kueri daftar pengguna dan playlist terkait, Anda harus terlebih dahulu memodelkan hubungan one-to-many di antara dua entity. Untuk melakukannya, buat class data baru tempat setiap instance memiliki instance parent entity dan daftar semua instance entity turunan yang sesuai. Tambahkan anotasi @Relation ke instance entity turunan, dengan parentColumn ditetapkan ke nama kolom kunci utama dari parent entity dan entityColumn ditetapkan ke nama kolom entity turunan yang mereferensikan kunci utama parent entity.

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;
}

Terakhir, tambahkan metode ke class DAO yang menampilkan semua instance class data yang menghubungkan parent entity dan entity turunan. Metode ini memerlukan Room untuk menjalankan dua kueri, jadi tambahkan anotasi @Transaction ke metode ini sehingga seluruh operasi dijalankan secara atomik.

Kotlin

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

Java

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

Menentukan hubungan many-to-many

Hubungan many-to-many antara dua entity adalah hubungan saat setiap instance parent entity tidak berkaitan sama sekali atau berkaitan dengan beberapa instance entity turunan, dan hal sebaliknya juga berlaku.

Dalam contoh aplikasi streaming musik, pertimbangkan lagu dalam playlist yang ditentukan pengguna. Setiap playlist dapat berisi banyak lagu, dan setiap lagu dapat menjadi bagian dari playlist yang berbeda. Oleh karena itu, ada hubungan many-to-many antara entity Playlist dan entity Song.

Untuk menentukan hubungan many-to-many, buat class untuk kedua entity Anda terlebih dahulu. Hubungan many-to-many berbeda dengan jenis hubungan lainnya, karena secara umum tidak ada referensi ke parent entity dalam entity turunan. Sebaliknya, buat class ketiga untuk merepresentasikan entity asosiatif, atau tabel referensi silang antara dua entity. Tabel referensi silang harus memiliki kolom untuk kunci utama dari setiap entity dalam hubungan many-to-many yang direpresentasikan dalam tabel. Dalam contoh ini, setiap baris dalam tabel referensi silang berkaitan dengan pasangan instance Playlist dan instance Song tempat lagu yang dirujuk disertakan dalam playlist yang direferensikan.

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;
}

Langkah berikutnya bergantung pada cara Anda ingin membuat kueri entity terkait ini.

  • Jika Anda ingin membuat kueri playlist dan daftar lagu terkait untuk setiap playlist, buat class data baru yang berisi satu objek Playlist dan daftar semua objek Song yang disertakan dalam playlist.
  • Jika Anda ingin membuat kueri lagu dan daftar playlist yang sesuai untuk setiap lagu, buat class data baru yang berisi satu objek Song dan daftar semua objek Playlist tempat lagu disertakan.

Dalam kedua kasus tersebut, buat model hubungan antara entity dengan menggunakan properti associateBy dalam anotasi @Relation di setiap class ini untuk mengidentifikasi entity referensi silang yang menyediakan hubungan antara entity Playlist dan entity 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;
}

Terakhir, tambahkan metode ke class DAO untuk menampilkan fungsi kueri yang diperlukan aplikasi Anda.

  • getPlaylistsWithSongs: metode ini membuat kueri database dan menampilkan semua objek PlaylistWithSongs yang dihasilkan.
  • getSongsWithPlaylists: metode ini membuat kueri database dan menampilkan semua objek SongWithPlaylists yang dihasilkan.

Masing-masing metode ini memerlukan Room untuk menjalankan dua kueri, jadi tambahkan anotasi @Transaction ke kedua metode tersebut sehingga seluruh operasi dilakukan secara atomik.

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();

Menentukan hubungan bertingkat

Terkadang, Anda mungkin perlu membuat kueri kumpulan tiga tabel atau lebih yang semuanya saling berkaitan. Dalam hal ini, Anda menentukan hubungan bertingkat antara tabel.

Misalkan dalam contoh aplikasi streaming musik, Anda ingin membuat kueri semua pengguna, semua playlist untuk setiap pengguna, dan semua lagu di setiap playlist untuk setiap pengguna. Pengguna memiliki hubungan one-to-many dengan playlist, dan playlist memiliki hubungan many-to-many dengan lagu. Contoh kode berikut menunjukkan class yang mewakili entity ini, serta tabel referensi silang untuk hubungan many-to-many antara playlist dan lagu:

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;
}

Pertama, buat model hubungan antara dua tabel dalam kumpulan seperti yang biasa Anda lakukan, menggunakan class data dan anotasi @Relation. Contoh berikut menunjukkan class PlaylistWithSongs yang memodelkan hubungan many-to-many antara class entity Playlist dan class entity Song:

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;
}

Setelah Anda menentukan class data yang merepresentasikan hubungan ini, buat class data lain yang memodelkan hubungan antara tabel lain dari kumpulan dan class hubungan pertama, "menyarangkan" hubungan yang ada ke dalam hubungan yang baru. Contoh berikut menunjukkan class UserWithPlaylistsAndSongs yang memodelkan hubungan one-to-many di antara class entity User dan class hubungan PlaylistWithSongs:

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;
}

Class UserWithPlaylistsAndSongs secara tidak langsung memodelkan hubungan antara ketiga class entity: User, Playlist, dan Song. Hal ini diilustrasikan dalam gambar 1.

UserWithPlaylistsAndSongs memodelkan hubungan antara User dan
  PlaylistWithSongs, yang kemudian memodelkan hubungan antara Playlist
  dan Song.

Gambar 1. Diagram hubungan class dalam contoh aplikasi streaming musik.

Jika ada tabel lainnya di set Anda, buat class untuk memodelkan hubungan antara setiap tabel yang tersisa dan class hubungan yang memodelkan hubungan antara semua tabel sebelumnya. Hal ini akan membuat rantai hubungan bertingkat di antara semua tabel yang ingin Anda kueri.

Terakhir, tambahkan metode ke class DAO untuk menampilkan fungsionalitas kueri yang diperlukan oleh aplikasi Anda. Metode ini memerlukan Room untuk menjalankan beberapa kueri, jadi tambahkan anotasi @Transaction sehingga seluruh operasi dijalankan secara atomik:

Kotlin

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

Java

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

Referensi lainnya

Untuk mempelajari lebih lanjut cara menentukan hubungan antara entity di Room, lihat referensi tambahan berikut.

Contoh

Video

Blog