Room 可轉換原始和 Box 類型資料,但不允許實體之間的物件參照。本文說明如何使用類型轉換器,以及 Room 不支援物件參照的原因。
使用類型轉換器
有時候,應用程式需要將自訂資料類型儲存於單一資料庫欄位。透過提供類型轉換器,應用程式即可支援自訂類型。類型轉換器會告知 Room 如何在 Room 可保留的已知類型與自訂類型之間轉換的方法。請使用 @TypeConverter
註解來識別類型轉換器。
假設開發人員需要保留 Room 資料庫中 Date
的執行個體,而 Room 不知道如何保留 Date
物件,那就需要定義類型轉換器:
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(); } }
此範例定義兩種轉換器方法,一種是將 Date
物件轉換成 Long
物件,另一種則是從 Long
到 Date
的反向轉換。由於 Room 知道如何保留 Long
物件,因此可以使用這些轉換器保留 Date
物件。
接下來,請在 AppDatabase
類別註解 @TypeConverters
,讓 Room 知道開發人員已定義的轉換器類別:
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(); }
定義這些類型轉換器後,即可在實體和 DAO 中使用自訂類型,方法與使用原始類型一樣:
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); }
在這個範例中,因為 AppDatabase
已註解 @TypeConverters
,所以 Room 可以在所有位置使用已定義的類型轉換器。不過,開發人員也可以使用 @TypeConverters
註解 @Entity
或 @Dao
類別,進而將轉換器範圍限定為特定實體或 DAO。
控制類型轉換器初始化
一般而言,Room 會自動處理類型轉換器的執行個體化。但是,開發人員有時可能需要傳送額外的依附元件至類型轉換器類別,也就是說,應用程式需要直接控制類型轉換器的初始化。在這種情況下,請在轉換類別中註解 @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) { ... } }
接著,除了在 @TypeConverters
中宣告轉換器類別之外,也可以使用 RoomDatabase.Builder.addTypeConverter()
方法,將轉換器類別的執行個體傳送至 RoomDatabase
建構工具:
Kotlin
val db = Room.databaseBuilder(...) .addTypeConverter(exampleConverterInstance) .build()
Java
AppDatabase db = Room.databaseBuilder(...) .addTypeConverter(exampleConverterInstance) .build();
瞭解聊天室不接受參照物件的原因
要點:Room 不允許在實體類別之間參照物件。開發人員必須改為明確要求,以取得應用程式所需的資料。
常見做法是參照從資料庫至個別物件模型之間的關聯,這種做法在伺服器端相當有用。即使程式載入存取欄位,伺服器仍可正常運作。
但是,這種延遲載入不適用於用戶端,因為這類活動通常發生在 UI 執行緒上,而且使用 UI 執行緒查詢磁碟上的資訊,將會嚴重影響效能。UI 執行緒通常有大約 16 毫秒的時間,以計算及繪製活動更新的版面配置。因此,即便查詢只需 5 毫秒,應用程式在執行時,仍可能有時間不足的問題,導致效能明顯變慢。如果執行緒同時執行其他作業,或是裝置正在執行其他工作,因而占用大量磁碟空間,那麼就會需要更多時間才能完成查詢。如果開發人員未使用延遲載入功能,則應用程式擷取的資料可能會超乎原本所需,因而產生記憶體用量問題。
物件關係參照通常取決於開發人員,以便他們根據應用程式功能,決定最佳做法。一般而言,開發人員會決定共用應用程式和 UI 之間的模型。然而,UI 會隨著時間改變,共用模型也會產生問題,開發人員更無法預測並偵錯,因此這個解決方案無法一勞永逸。
比方說,假設 UI 會載入 Book
物件清單,而每本書都有 Author
物件。開發人員可以先設計查詢方法,使用延遲載入來讓 Book
執行個體擷取作者。第一次擷取 author
欄位時,應用程式會查詢資料庫。不久之後,就能看到應用程式的 UI 也需要顯示作者名稱。如要輕鬆存取該名稱,請參閱以下程式碼範例:
Kotlin
authorNameTextView.text = book.author.name
Java
authorNameTextView.setText(book.getAuthor().getName());
儘管這項變更並不明顯,卻會使主執行緒查詢 Author
資料表。
如果預先查詢作者資訊,而稍後就不再用到它,那麼之後會難以改變資料載入的方式。舉例來說,儘管應用程式的 UI 不再需要顯示 Author
資訊,應用程式依舊會快速載入這些資訊,就會浪費寶貴的記憶體空間。如果 Author
類別參照了其他如 Books
這類的資料表,應用程式的效率就會更慢。
如果要使用 Room 並同時參照多個實體,請建立並改用包含所有實體的 POJO,然後針對彙整的對應資料表編寫查詢。這款模型結構完善,加上 Room 強大的查詢驗證功能,載入資料時更節省資源,進而提升應用程式的效能和使用者體驗。