Mengakses data menggunakan DAO Room

Untuk mengakses data aplikasi menggunakan library persistensi Room, gunakan objek akses data (data access objects), atau DAO. Kumpulan objek Daoini 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:

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

Codelab

Blog