Mereferensikan data kompleks menggunakan Room

Room menyediakan fungsionalitas untuk melakukan konversi antara jenis klasik dan jenis yang dapat ditetapkan pada objek, tetapi tidak memungkinkan referensi objek antar-entity. Dokumen ini menjelaskan cara menggunakan konverter jenis dan alasan mengapa Room tidak mendukung referensi objek.

Menggunakan konverter jenis

Terkadang, aplikasi Anda perlu menyimpan jenis data kustom dalam satu kolom database. Anda mendukung jenis kustom dengan menyediakan pengonversi jenis, yang merupakan metode yang memberi tahu Room cara mengonversi jenis kustom ke dan dari jenis umum yang dapat dipertahankan Room. Anda dapat mengidentifikasi pengonversi jenis menggunakan anotasi @TypeConverter.

Misalnya, Anda perlu mempertahankan instance Date di database Room. Room tidak tahu cara mempertahankan objek Date, sehingga Anda perlu menentukan konverter jenis:

Kotlin

class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? {
    return value?.let { Date(it) }
  }

  @TypeConverter
  fun dateToTimestamp(date: Date?): Long? {
    return date?.time?.toLong()
  }
}

Java

public class Converters {
  @TypeConverter
  public static Date fromTimestamp(Long value) {
    return value == null ? null : new Date(value);
  }

  @TypeConverter
  public static Long dateToTimestamp(Date date) {
    return date == null ? null : date.getTime();
  }
}

Contoh ini menetapkan dua metode konverter jenis: metode yang mengonversi objek Date menjadi objek Long, dan metode lainnya yang melakukan konversi terbalik dari Long menjadi Date. Karena Room tahu cara mempertahankan objek Long, pengonversi dapat menggunakan pengonversi ini untuk mempertahankan objek Date.

Selanjutnya, tambahkan anotasi @TypeConverters ke class AppDatabase sehingga Room mengetahui class pengonversi yang telah Anda tentukan:

Kotlin

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
  abstract fun userDao(): UserDao
}

Java

@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}

Dengan konverter jenis yang ditentukan ini, Anda dapat menggunakan jenis kustom di entity dan DAO seperti Anda menggunakan jenis sederhana:

Kotlin

@Entity
data class User(private val birthday: Date?)

@Dao
interface UserDao {
  @Query("SELECT * FROM user WHERE birthday = :targetDate")
  fun findUsersBornOnDate(targetDate: Date): List<User>
}

Java

@Entity
public class User {
  private Date birthday;
}

@Dao
public interface UserDao {
  @Query("SELECT * FROM user WHERE birthday = :targetDate")
  List<User> findUsersBornOnDate(Date targetDate);
}

Dalam contoh ini, Room dapat menggunakan pengonversi jenis yang ditentukan di mana saja karena Anda menganotasi AppDatabase dengan @TypeConverters. Namun, Anda juga dapat mencakup pengonversi jenis ke entity atau DAO tertentu dengan menganotasi class @Entity atau @Dao dengan @TypeConverters.

Menginisialisasi pengonversi jenis kontrol

Biasanya, Room menangani pembuatan instance pengonversi jenis untuk Anda. Namun, terkadang Anda mungkin perlu meneruskan dependensi tambahan ke class konverter jenis, yang berarti Anda memerlukan aplikasi untuk mengontrol inisialisasi konverter jenis secara langsung. Dalam hal ini, anotasikan class pengonversi dengan @ProvidedTypeConverter:

Kotlin

@ProvidedTypeConverter
class ExampleConverter {
  @TypeConverter
  fun StringToExample(string: String?): ExampleType? {
    ...
  }

  @TypeConverter
  fun ExampleToString(example: ExampleType?): String? {
    ...
  }
}

Java

@ProvidedTypeConverter
public class ExampleConverter {
  @TypeConverter
  public Example StringToExample(String string) {
    ...
  }

  @TypeConverter
  public String ExampleToString(Example example) {
    ...
  }
}

Kemudian, selain mendeklarasikan class pengonversi di @TypeConverters, gunakan metode RoomDatabase.Builder.addTypeConverter() untuk meneruskan instance class pengonversi ke builder RoomDatabase:

Kotlin

val db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build()

Java

AppDatabase db = Room.databaseBuilder(...)
  .addTypeConverter(exampleConverterInstance)
  .build();

Memahami mengapa Room tidak mengizinkan referensi objek

Poin utama: Room tidak mengizinkan referensi objek antar-class entity. Sebagai gantinya, Anda harus secara eksplisit meminta data yang dibutuhkan oleh aplikasi Anda.

Memetakan hubungan dari database ke masing-masing model objek adalah praktik yang umum dilakukan dan bekerja dengan sangat baik di sisi server. Bahkan, saat program memuat kolom ketika sedang diakses, performa server akan tetap baik.

Namun, di sisi klien, jenis pemuatan lambat ini akan menyulitkan karena biasanya akan terjadi di UI thread, dan meminta informasi tentang disk di UI thread akan menimbulkan masalah performa yang signifikan. UI thread biasanya membutuhkan waktu sekitar 16 milidetik untuk menghitung dan menggambar tata letak aktivitas yang diperbarui. Jadi, meskipun kueri hanya memerlukan waktu 5 milidetik, aplikasi Anda kemungkinan akan kehabisan waktu untuk menggambar frame sehingga gangguan visual yang cukup terlihat akan muncul. Kueri dapat memerlukan lebih banyak waktu untuk diselesaikan jika ada transaksi terpisah yang berjalan secara paralel, atau jika perangkat sedang menjalankan tugas lain yang membutuhkan banyak disk. Namun, jika Anda tidak menggunakan pemuatan lambat, aplikasi akan mengambil lebih banyak data daripada yang dibutuhkan sehingga akan timbul masalah pada penggunaan memori.

Pemetaan terkait objek biasanya menyerahkan keputusan ini kepada developer agar mereka dapat melakukan yang terbaik sesuai kasus penggunaan aplikasinya. Developer biasanya memutuskan untuk menggunakan model yang sama antara aplikasi mereka dan UI-nya. Namun, solusi ini kurang sesuai karena seiring dengan berubahnya UI dari waktu ke waktu, model bersama akan menimbulkan masalah yang sulit diantisipasi dan di-debug oleh developer.

Misalnya, terdapat UI yang memuat daftar objek Book, yang masing-masing memiliki objek Author. Awalnya, Anda mungkin mendesain kueri untuk menggunakan pemuatan lambat agar instance Book mengambil penulisnya. Pengambilan pertama kolom author akan membuat kueri pada database. Beberapa waktu kemudian, Anda menyadari bahwa nama penulis juga perlu ditampilkan dalam UI aplikasi. Anda dapat mengakses nama ini dengan mudah, seperti dalam cuplikan kode berikut:

Kotlin

authorNameTextView.text = book.author.name

Java

authorNameTextView.setText(book.getAuthor().getName());

Namun, perubahan yang tampaknya tidak berbahaya ini menyebabkan tabel Author dikueri pada thread utama.

Jika Anda meminta informasi penulis lebih awal, akan sulit untuk mengubah cara data dimuat ketika Anda tidak lagi membutuhkannya. Misalnya, jika UI aplikasi Anda tidak perlu lagi menampilkan informasi Author, aplikasi Anda akan secara efektif memuat data yang tidak lagi ditampilkan, dan menghabiskan ruang memori yang berharga. Efisiensi aplikasi Anda akan terus turun jika class Author merujuk ke tabel lain, seperti Books.

Untuk mereferensikan beberapa entity sekaligus menggunakan Room, Anda dapat membuat POJO yang berisi setiap entity, lalu menulis kueri yang menggabungkan tabel terkait. Model yang terstruktur dengan baik ini, bersama kemampuan validasi kueri Room yang canggih, memungkinkan aplikasi Anda menggunakan lebih sedikit resource saat memuat data sehingga performa aplikasi dan pengalaman pengguna pun akan meningkat.