Mengakses data menggunakan DAO Room

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, yaitu kueri yang menghasilkan 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 @Insert hanya menerima satu parameter, metode tersebut dapat menghasilkan long, yang merupakan rowId baru untuk item yang disisipkan. Jika parameter berupa array atau kumpulan, metode ini akan menghasilkan long[] atau List<Long>.

Untuk detail selengkapnya, lihat dokumentasi referensi untuk anotasi @Insert, serta dokumentasi SQLite untuk tabel rowid.

Update

Metode kemudahan Update memodifikasi kumpulan entitas yang ditentukan 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 mengembalikan nilai int, yang menunjukkan jumlah baris yang diupdate dalam database.

Delete

Metode kemudahan Delete menghapus kumpulan entitas yang ditentukan 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 mengembalikan 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 paham bahwa kueri tersebut mengembalikan nilai untuk kolom first_name dan last_name, dan nilai ini dapat dipetakan ke dalam kolom class NameTuple. Oleh karena itu, Room dapat mengembalikan kode yang tepat. Jika kueri mengembalikan terlalu banyak kolom, atau mengembalikan kolom yang tidak ada dalam 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);
    }
    

Kueri yang dapat diamati

Saat menjalankan kueri, Anda sering kali ingin UI aplikasi turut diupdate secara otomatis saat datanya berubah. Untuk melakukannya, 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 dalam 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.

Akses kursor langsung

Jika logika aplikasi memerlukan akses langsung ke baris hasil, Anda dapat mengembalikan objek Cursor dari kueri seperti pada 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. Selain itu, 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;
       }
    }
    

Menulis metode asinkron dengan coroutines 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 coroutines Kotlin dalam aplikasi, lihat Meningkatkan performa aplikasi dengan coroutines Kotlin.