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 objekSong
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 objekPlaylist
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 objekPlaylistWithSongs
yang dihasilkan.getSongsWithPlaylists
: metode ini membuat kueri database dan menampilkan semua objekSongWithPlaylists
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.](https://developer.android.com/static/images/training/data-storage/room_nested_relationships.png?authuser=9&hl=id)
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
- Yang Baru di Room (Android Dev Summit '19)