Untuk mengakses data aplikasi menggunakan library persistensi Room, gunakan objek akses data (data access objects), atau DAO. Kumpulan objek Dao
ini membentuk komponen utama Room, karena setiap DAO menyertakan metode yang menawarkan akses abstrak ke database aplikasi Anda.
Dengan mengakses database menggunakan class DAO, bukan builder kueri atau kueri langsung, Anda dapat memisahkan berbagai komponen arsitektur database. Selain itu, DAO juga memungkinkan Anda meniru akses database dengan mudah saat menguji aplikasi.
DAO dapat berupa antarmuka atau class abstrak. Jika berupa class abstrak, secara opsional DAO dapat memiliki konstruktor yang menggunakan RoomDatabase
sebagai parameter tunggalnya. Room membuat setiap penerapan DAO pada waktu kompilasi.
Catatan: Room tidak mendukung akses database pada thread utama kecuali Anda telah memanggil allowMainThreadQueries()
pada builder karena dapat mengunci UI untuk jangka waktu yang lama. Kueri asinkron—kueri yang menampilkan instance LiveData
atau Flowable
—dikecualikan dari aturan ini karena akan menjalankan kueri secara asinkron di thread latar belakang saat diperlukan.
Menentukan metode untuk kemudahan
Ada beberapa kueri kemudahan yang dapat Anda representasikan menggunakan class DAO. Dokumen ini menyertakan beberapa contoh umum.
Insert
Saat Anda membuat metode DAO dan menganotasikannya dengan @Insert
Room akan menghasilkan penerapan yang menyisipkan semua parameter ke database dalam satu kali transaksi.
Cuplikan kode berikut menunjukkan beberapa kueri contoh:
Kotlin
@Dao interface MyDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertUsers(vararg users: User) @Insert fun insertBothUsers(user1: User, user2: User) @Insert fun insertUsersAndFriends(user: User, friends: List<User>) }
Java
@Dao public interface MyDao { @Insert(onConflict = OnConflictStrategy.REPLACE) public void insertUsers(User... users); @Insert public void insertBothUsers(User user1, User user2); @Insert public void insertUsersAndFriends(User user, List<User> friends); }
Jika metode @Insert
hanya menerima 1 parameter, metode tersebut dapat menampilkan long
, yang merupakan rowId
baru untuk item yang disisipkan. Jika parameter berupa array atau kumpulan, metode ini akan menampilkan long[]
atau List<Long>
.
Untuk detail selengkapnya, lihat dokumentasi referensi untuk anotasi @Insert
, serta dokumentasi SQLite untuk tabel rowid.
Update
Metode praktis Update
mengubah sekumpulan entitas, yang ditetapkan sebagai parameter, dalam database. Metode ini menggunakan kueri yang melakukan pencocokan dengan kunci utama setiap entitas.
Cuplikan kode berikut menunjukkan cara menentukan metode ini:
Kotlin
@Dao interface MyDao { @Update fun updateUsers(vararg users: User) }
Java
@Dao public interface MyDao { @Update public void updateUsers(User... users); }
Meskipun biasanya tidak diperlukan, Anda dapat mengatur metode ini agar menampilkan nilai int
, yang menunjukkan jumlah baris yang diupdate dalam database.
Hapus
Metode praktis Delete
menghapus sekumpulan entitas, yang ditetapkan sebagai parameter, dari database. Metode ini menggunakan kunci utama untuk menemukan entitas yang akan dihapus.
Cuplikan kode berikut menunjukkan cara menentukan metode ini:
Kotlin
@Dao interface MyDao { @Delete fun deleteUsers(vararg users: User) }
Java
@Dao public interface MyDao { @Delete public void deleteUsers(User... users); }
Meskipun biasanya tidak diperlukan, Anda dapat mengatur metode ini agar menampilkan nilai int
, yang menunjukkan jumlah baris yang dihapus dari database.
Meminta informasi
@Query
adalah anotasi utama yang digunakan dalam class DAO. Metode ini memungkinkan Anda melakukan operasi baca/tulis pada database. Setiap metode @Query
akan diverifikasi pada waktu kompilasi sehingga jika ada masalah dengan kueri, yang terjadi adalah error kompilasi, bukan kegagalan waktu proses.
Room juga memverifikasi nilai hasil dari kueri sehingga jika nama kolom dalam objek yang dihasilkan tidak cocok dengan nama kolom yang terkait dalam respons kueri, Room akan memberikan peringatan dengan salah satu cara berikut:
- Memberikan peringatan jika hanya ada beberapa nama kolom yang cocok.
- Memberikan error jika tidak ada nama kolom yang cocok.
Kueri sederhana
Kotlin
@Dao interface MyDao { @Query("SELECT * FROM user") fun loadAllUsers(): Array<User> }
Java
@Dao public interface MyDao { @Query("SELECT * FROM user") public User[] loadAllUsers(); }
Contoh ini menunjukkan kueri sangat sederhana yang memuat semua pengguna. Pada waktu kompilasi, Room tahu bahwa kueri tersebut meminta semua kolom dalam tabel pengguna. Jika kueri berisi error sintaksis, atau jika tabel pengguna tidak ada dalam database, Room akan menampilkan error dengan pesan yang terkait ketika aplikasi Anda dikompilasi.
Meneruskan parameter ke kueri
Biasanya, Anda harus meneruskan parameter ke kueri untuk melakukan operasi pemfilteran, seperti untuk menampilkan pengguna yang lebih tua dari usia tertentu. Untuk menyelesaikan tugas ini, gunakan parameter metode dalam anotasi Room seperti pada cuplikan kode berikut:
Kotlin
@Dao interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge") fun loadAllUsersOlderThan(minAge: Int): Array<User> }
Java
@Dao public interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge") public User[] loadAllUsersOlderThan(int minAge); }
Saat kueri ini diproses pada waktu kompilasi, Room akan mencocokkan parameter pengikat :minAge
dengan parameter metode minAge
. Room melakukan pencocokan menggunakan nama parameter. Jika terdapat ketidakcocokan, error akan terjadi saat aplikasi Anda dikompilasi.
Anda juga dapat meneruskan beberapa parameter atau mereferensikannya beberapa kali dalam kueri, seperti pada cuplikan kode berikut:
Kotlin
@Dao interface MyDao { @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User> @Query("SELECT * FROM user WHERE first_name LIKE :search " + "OR last_name LIKE :search") fun findUserWithName(search: String): List<User> }
Java
@Dao public interface MyDao { @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") public User[] loadAllUsersBetweenAges(int minAge, int maxAge); @Query("SELECT * FROM user WHERE first_name LIKE :search " + "OR last_name LIKE :search") public List<User> findUserWithName(String search); }
Mengembalikan subkumpulan kolom
Biasanya, Anda hanya perlu mendapatkan beberapa kolom dari sebuah entitas. Misalnya, UI Anda mungkin hanya menampilkan nama depan dan nama belakang pengguna, bukan setiap detail tentang pengguna tersebut. Dengan mengambil kolom yang muncul dalam UI aplikasi saja, Anda dapat menghemat resource dan kueri akan selesai lebih cepat.
Room memungkinkan Anda untuk mengembalikan objek berbasis Java apa pun dari kueri, asalkan kumpulan kolom hasil dapat dipetakan ke objek yang dikembalikan. Sebagai contoh, Anda dapat membuat objek klasik biasa berbasis Java (POJO) berikut untuk mengambil nama depan dan nama belakang pengguna:
Kotlin
data class NameTuple( @ColumnInfo(name = "first_name") val firstName: String?, @ColumnInfo(name = "last_name") val lastName: String? )
Java
public class NameTuple { @ColumnInfo(name = "first_name") public String firstName; @ColumnInfo(name = "last_name") @NonNull public String lastName; }
Sekarang, Anda dapat menggunakan POJO ini dalam metode kueri:
Kotlin
@Dao interface MyDao { @Query("SELECT first_name, last_name FROM user") fun loadFullName(): List<NameTuple> }
Java
@Dao public interface MyDao { @Query("SELECT first_name, last_name FROM user") public List<NameTuple> loadFullName(); }
Room memahami bahwa kueri tersebut menampilkan nilai untuk kolom first_name
dan last_name
, serta bahwa nilai ini dapat dipetakan ke dalam kolom class NameTuple
. Oleh karena itu, Room dapat menampilkan kode yang tepat. Jika kueri menampilkan terlalu banyak kolom, atau menampilkan kolom yang tidak ada di class NameTuple
, Room akan menampilkan peringatan.
Meneruskan sekumpulan argumen
Beberapa kueri mungkin mengharuskan Anda untuk meneruskan parameter dalam jumlah yang tidak tentu, dengan jumlah tepat yang tidak diketahui hingga waktu proses. Sebagai contoh, Anda mungkin ingin mengambil informasi tentang semua pengguna dari satu subkumpulan wilayah. Room dapat mengetahui saat suatu parameter merepresentasikan sebuah kumpulan, dan akan otomatis memperluasnya pada waktu proses berdasarkan jumlah parameter yang diberikan.
Kotlin
@Dao interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") fun loadUsersFromRegions(regions: List<String>): List<NameTuple> }
Java
@Dao public interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") public List<NameTuple> loadUsersFromRegions(List<String> regions); }
Akses kursor langsung
Jika logika aplikasi Anda memerlukan akses langsung ke baris hasil, Anda dapat menampilkan objek Cursor
dari kueri Anda, seperti yang ditampilkan dalam cuplikan kode berikut:
Kotlin
@Dao interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") fun loadRawUsersOlderThan(minAge: Int): Cursor }
Java
@Dao public interface MyDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") public Cursor loadRawUsersOlderThan(int minAge); }
Perhatian: Sangat tidak dianjurkan untuk bekerja dengan Cursor API karena API ini tidak menjamin apakah baris memang ada dan nilai apa yang ada dalam baris tersebut. Gunakan fungsionalitas ini hanya jika Anda sudah memiliki kode yang mengharapkan kursor dan tidak dapat difaktorkan ulang dengan mudah.
Meminta beberapa tabel
Beberapa kueri mungkin memerlukan akses ke beberapa tabel untuk menghitung hasilnya. Room memungkinkan Anda menulis kueri apa pun sehingga Anda juga dapat menggabungkan tabel.
Lebih lanjut, jika respons yang diberikan berupa jenis data yang dapat diamati, seperti Flowable
atau LiveData
, Room akan melihat semua tabel yang direferensikan dalam kueri untuk pembatalan validasi.
Cuplikan kode berikut menunjukkan cara menggabungkan tabel untuk menyatukan informasi antara tabel pengguna yang meminjam buku dengan tabel data buku yang saat ini dipinjam:
Kotlin
@Dao interface MyDao { @Query( "SELECT * FROM book " + "INNER JOIN loan ON loan.book_id = book.id " + "INNER JOIN user ON user.id = loan.user_id " + "WHERE user.name LIKE :userName" ) fun findBooksBorrowedByNameSync(userName: String): List<Book> }
Java
@Dao public interface MyDao { @Query("SELECT * FROM book " + "INNER JOIN loan ON loan.book_id = book.id " + "INNER JOIN user ON user.id = loan.user_id " + "WHERE user.name LIKE :userName") public List<Book> findBooksBorrowedByNameSync(String userName); }
Anda juga dapat mengembalikan POJO dari kueri ini. Contohnya, Anda dapat menulis kueri yang memuat pengguna dan nama hewan peliharaannya seperti berikut:
Kotlin
@Dao interface MyDao { @Query( "SELECT user.name AS userName, pet.name AS petName " + "FROM user, pet " + "WHERE user.id = pet.user_id" ) fun loadUserAndPetNames(): LiveData<List<UserPet>> // You can also define this class in a separate file. data class UserPet(val userName: String?, val petName: String?) }
Java
@Dao public interface MyDao { @Query("SELECT user.name AS userName, pet.name AS petName " + "FROM user, pet " + "WHERE user.id = pet.user_id") public LiveData<List<UserPet>> loadUserAndPetNames(); // You can also define this class in a separate file, as long as you add the // "public" access modifier. static class UserPet { public String userName; public String petName; } }
Jenis nilai yang ditampilkan kueri
Room mendukung berbagai jenis yang ditampilkan untuk metode kueri, termasuk jenis nilai khusus yang ditampilkan untuk interoperabilitas dengan framework atau API spesifik. Tabel berikut menampilkan jenis nilai yang dapat ditampilkan, yang dapat diterapkan berdasarkan jenis kueri dan framework:
Jenis kueri | Coroutine | RxJava | Guava | Lifecycle |
---|---|---|---|---|
Tindakan baca yang dapat diamati | Flow<T> |
Flowable<T> , Publisher<T> , Observable<T> |
T/A | LiveData<T> |
Tindakan baca satu kali | suspend fun |
Single<T> , Maybe<T> |
ListenableFuture<T> |
T/A |
Tindakan tulis satu kali | suspend fun |
Single<T> , Maybe<T> , Completable<T> |
ListenableFuture<T> |
T/A |
Kueri reaktif dengan Flow
Pada Room 2.2 dan yang lebih tinggi, Anda dapat memastikan bahwa UI aplikasi Anda tetap terupdate dengan menggunakan fungsionalitas Flow
Kotlin. Untuk membuat UI otomatis diupdate saat data pokok berubah, tulis metode kueri yang menampilkan objek Flow
:
@Query("SELECT * FROM User")
fun getAllUsers(): Flow<List<User>>
Setiap kali data dalam tabel berubah, objek Flow
yang ditampilkan akan memicu kueri lagi dan memunculkan ulang seluruh kumpulan hasil.
Kueri reaktif yang menggunakan Flow
memiliki satu batasan penting: objek Flow
akan membuat ulang kueri setiap kali baris dalam tabel diperbarui, terlepas dari apakah baris tersebut berada dalam kumpulan hasil atau tidak. Anda dapat memastikan bahwa UI hanya diberi tahu jika hasil kueri yang sebenarnya berubah dengan menerapkan operator distinctUntilChanged()
ke objek Flow
yang ditampilkan:
@Dao
abstract class UsersDao {
@Query("SELECT * FROM User WHERE username = :username")
abstract fun getUser(username: String): Flow<User>
fun getUserDistinctUntilChanged(username:String) =
getUser(username).distinctUntilChanged()
}
Kueri Asinkron dengan coroutine Kotlin
Anda dapat menambahkan kata kunci suspend
dari Kotlin ke metode DAO untuk menjadikannya asinkron menggunakan fungsionalitas coroutines Kotlin. Hal ini memastikan bahwa metode tersebut tidak dapat dijalankan pada thread utama.
@Dao
interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUsers(vararg users: User)
@Update
suspend fun updateUsers(vararg users: User)
@Delete
suspend fun deleteUsers(vararg users: User)
@Query("SELECT * FROM user")
suspend fun loadAllUsers(): Array<User>
}
Panduan ini juga berlaku untuk metode DAO yang dianotasikan dengan @Transaction
. Anda dapat menggunakan fitur ini untuk membuat metode penangguhan database dari metode DAO lainnya. Metode ini kemudian berjalan dalam satu transaksi database.
@Dao
abstract class UsersDao {
@Transaction
open suspend fun setLoggedInUser(loggedInUser: User) {
deleteUser(loggedInUser)
insertUser(loggedInUser)
}
@Query("DELETE FROM users")
abstract fun deleteUser(user: User)
@Insert
abstract suspend fun insertUser(user: User)
}
Untuk informasi selengkapnya tentang penggunaan coroutine Kotlin dalam aplikasi, lihat Meningkatkan performa aplikasi dengan coroutine Kotlin.
Kueri yang dapat diamati dengan LiveData
Saat menjalankan kueri, Anda sering kali ingin UI aplikasi turut diupdate secara otomatis saat datanya berubah. Untuk melakukan ini, gunakan nilai hasil dari jenis LiveData
dalam deskripsi metode kueri. Room akan menghasilkan semua kode yang diperlukan untuk mengupdate LiveData
saat database diupdate.
Kotlin
@Dao interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>> }
Java
@Dao public interface MyDao { @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions); }
Kueri reaktif dengan RxJava
Room menyediakan dukungan berikut untuk nilai hasil dari jenis RxJava2:
- Metode
@Query
: Room mendukung nilai hasil dari jenisPublisher
,Flowable
, danObservable
. - Metode
@Insert
,@Update
, dan@Delete
: Room 2.1.0 dan yang lebih tinggi mendukung nilai hasil dari jenisCompletable
,Single<T>
, danMaybe<T>
.
Untuk menggunakan fungsionalitas ini, sertakan versi terbaru artefak rxjava2
di file build.gradle
aplikasi Anda:
app/build.gradle
dependencies { def room_version = "2.1.0" implementation 'androidx.room:room-rxjava2:$room_version' }
Untuk melihat versi terbaru library ini, lihat informasi tentang Room di halaman versi.
Cuplikan kode berikut menunjukkan beberapa contoh tentang bagaimana Anda dapat menggunakan jenis pengembalian ini:
Kotlin
@Dao interface MyDao { @Query("SELECT * from user where id = :id LIMIT 1") fun loadUserById(id: Int): Flowable<User> // Emits the number of users added to the database. @Insert fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int> // Makes sure that the operation finishes successfully. @Insert fun insertLargeNumberOfUsers(varargs users: User): Completable /* Emits the number of users removed from the database. Always emits at least one user. */ @Delete fun deleteAllUsers(users: List<User>): Single<Int> }
Java
@Dao public interface MyDao { @Query("SELECT * from user where id = :id LIMIT 1") public Flowable<User> loadUserById(int id); // Emits the number of users added to the database. @Insert public Maybe<Integer> insertLargeNumberOfUsers(List<User> users); // Makes sure that the operation finishes successfully. @Insert public Completable insertLargeNumberOfUsers(User... users); /* Emits the number of users removed from the database. Always emits at least one user. */ @Delete public Single<Integer> deleteUsers(List<User> users); }
Untuk detail selengkapnya, lihat artikel Room dan RxJava Google Developers.
Referensi lainnya
Untuk mempelajari lebih lanjut cara mengakses data menggunakan DAO Room, lihat referensi tambahan berikut.
Contoh
- Android Sunflower
- Sampel Room & RxJava (Java) (Kotlin)