يمكن لـ Room تحويل الأنواع الأساسية والأنواع المغلَّفة، ولكنّه لا يسمح بالإشارات إلى الكائنات بين الكيانات. تعرَّف على كيفية استخدام محوّلات الأنواع وسبب عدم توفُّر إمكانية الإشارات إلى الكائنات في Room.
استخدام محوّلات الأنواع
في بعض الأحيان، تحتاج إلى أن يخزّن تطبيقك نوع بيانات مخصّصًا في عمود واحد في قاعدة البيانات. يمكنك استخدام الأنواع المخصّصة من خلال توفير محوّلات الأنواع. وهي دوال تخبر Room بكيفية تحويل الأنواع المخصّصة إلى أنواع معروفة يمكن لـ Room الاحتفاظ بها والعكس. يمكنك تحديد محوّلات الأنواع باستخدام الشرح التوضيحي
@ColumnTypeConverter.
لنفترض أنّك بحاجة إلى الاحتفاظ بنسخ من Date في
قاعدة بيانات Room. لا يمكن لـ Room الاحتفاظ بكائنات Date بشكلٍ تلقائي، لذا عليك تحديد محوّلات الأنواع:
object Converters { @ColumnTypeConverter fun fromTimestamp(value: Long?): Date? { return value?.let { Date(it) } } @ColumnTypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time } }
يحدّد هذا المثال دالتَين لتحويل الأنواع: إحداهما تحوّل كائن Date إلى كائن Long، والأخرى تحوّل كائن Long إلى كائن Date. بما أنّ Room يمكنه الاحتفاظ بكائنات Long، يمكنه استخدام هذه المحوّلات للاحتفاظ بكائنات Date.
بعد ذلك، أضِف الشرح التوضيحي @ColumnTypeConverters
إلى فئة AppDatabase ليتمكّن Room من استخدام فئة المحوّل
التي حدّدتها:
@Database(entities = [User::class], version = 1) @ColumnTypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
بعد تحديد محوّلات الأنواع هذه، يمكنك استخدام النوع المخصّص في كياناتك وواجهات الوصول إلى البيانات (DAOs) تمامًا كما تستخدم الأنواع الأساسية:
@Entity data class User( @PrimaryKey val id: Long, val name: String, val birthday: Date? ) @Dao interface UserDao { @Query("SELECT * FROM user WHERE birthday = :targetDate") suspend fun findUsersBornOnDate(targetDate: Date): List<User> }
بما أنّك أضفت الشرح التوضيحي @ColumnTypeConverters إلى AppDatabase في هذا المثال، يمكن لـ Room استخدام محوّل الأنواع المحدّد في كل مكان. لتحديد نطاق محوّلات الأنواع لكيانات أو واجهات وصول إلى البيانات معيّنة بدلاً من ذلك، أضِف الشرح التوضيحي @ColumnTypeConverters إلى فئتَي @Entity أو @Dao.
التحكّم في تهيئة محوّل الأنواع
عادةً، ينشئ Room محوّلات الأنواع نيابةً عنك. ومع ذلك، إذا كنت بحاجة إلى تمرير تبعيات إضافية إلى فئات محوّل الأنواع، يجب أن يتحكّم تطبيقك بشكلٍ مباشر في تهيئتها. في هذه الحالة، أضِف الشرح التوضيحي إلى فئة المحوّل:@ProvidedColumnTypeConverter
@ProvidedColumnTypeConverter class ExampleConverter { @ColumnTypeConverter fun stringToExample(string: String?): ExampleType? { return string?.let { ExampleType() } } @ColumnTypeConverter fun exampleToString(example: ExampleType?): String? { return example?.toString() } }
بالإضافة إلى تعريف فئة المحوّل في @ColumnTypeConverters، استخدِم
الدالة RoomDatabase.Builder.addColumnTypeConverter لتمرير نسخة من فئة المحوّل إلى أداة إنشاء RoomDatabase
val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name") .addColumnTypeConverter(exampleConverterInstance) .build()
فهم سبب عدم توفُّر إمكانية الإشارات إلى الكائنات في Room
النقطة الأساسية: لا يسمح Room بالإشارات إلى الكائنات بين فئات الكيانات. بدلاً من ذلك، عليك طلب البيانات التي يحتاجها تطبيقك بشكلٍ صريح.
إنّ ربط العلاقات من قاعدة بيانات بنموذج الكائن المعنيّ هو ممارسة شائعة وتحقّق نتائج جيدة جدًا على جانب الخادم. حتى عندما يحمّل البرنامج الخصائص عند الوصول إليها، يظلّ أداء الخادم جيدًا.
ومع ذلك، من جهة العميل، لا يمكن استخدام هذا النوع من التحميل الكسول لأنّه يحدث عادةً في سلسلة واجهة المستخدم، ويؤدي طلب المعلومات على القرص في سلسلة واجهة المستخدم إلى حدوث مشاكل كبيرة في الأداء. عادةً ما يكون لدى سلسلة تعليمات واجهة المستخدم حوالي 16 ملي ثانية لحساب وتصوير التنسيق المعدَّل لنشاط معيّن، لذا حتى إذا استغرق طلب البحث 5 ملي ثانية فقط، فمن المرجّح أن ينتهي وقت تطبيقك قبل تصوير الإطار، ما يؤدي إلى حدوث أعطال مرئية ملحوظة. قد يستغرق طلب البحث وقتًا أطول لإكماله إذا كانت هناك معاملة منفصلة قيد التشغيل بالتوازي، أو إذا كان الجهاز ينفّذ مهام أخرى تتطلّب استخدام القرص بشكلٍ مكثّف. ومع ذلك، إذا لم تستخدِم التحميل المؤجّل، سيجلب تطبيقك بيانات أكثر مما يحتاج إليه، ما يؤدي إلى حدوث مشاكل في استهلاك الذاكرة.
عادةً ما تترك عمليات الربط بين الكائنات والعلاقات هذا القرار للمطوّرين ليتمكّنوا من اتّخاذ أفضل الخيارات لحالات استخدام تطبيقاتهم. عادةً ما يقرّر المطوّرون مشاركة النموذج بين تطبيقهم وواجهة المستخدم. ومع ذلك، لا يمكن توسيع نطاق هذا الحل بشكلٍ جيد، لأنّه مع تغيُّر واجهة المستخدم بمرور الوقت، يؤدي النموذج المشترَك إلى حدوث مشاكل يصعب على المطوّرين توقّعها وتصحيح أخطائها.
على سبيل المثال، لنفترض أنّ هناك واجهة مستخدم تحمّل قائمة بكائنات Book، ولكل كتاب كائن Author. قد تصمّم في البداية طلبات البحث لاستخدام التحميل المؤجّل لكي تستردّ نُسخ Book المؤلّف. يطلب الاسترداد الأول للسمة author قاعدة البيانات. بعد فترة، تلاحظ أنّك بحاجة إلى عرض اسم المؤلّف في واجهة مستخدم تطبيقك أيضًا. يمكنك الوصول إلى هذا الاسم، كما هو موضّح في مقتطف الرمز البرمجي التالي:
Text(text = book.author.name)
ومع ذلك، يؤدي هذا التغيير الذي يبدو غير ضار إلى طلب جدول Author في سلسلة التعليمات الرئيسية.
إذا طلبت معلومات المؤلّف مسبقًا ولكنّك لست بحاجة إليها، يصعب تغيير طريقة تحميل البيانات. على سبيل المثال، إذا لم تعُد واجهة مستخدم تطبيقك بحاجة إلى عرض معلومات Author، سيحمّل تطبيقك فعليًا بيانات لا يعرضها، ما يؤدي إلى إهدار مساحة قيّمة من الذاكرة. ينخفض مستوى كفاءة تطبيقك أكثر إذا كانت فئة Author تشير إلى جدول آخر، مثل Books.
للإشارة إلى كيانات متعدّدة في الوقت نفسه باستخدام Room، أنشئ كائن بيانات يحتوي على كل كيان، ثم اكتب طلب بحث يربط الجداول المقابلة. يسمح هذا النموذج المنظَّم جيدًا، بالإضافة إلى إمكانات التحقّق القوية من طلبات البحث في Room، لتطبيقك باستهلاك موارد أقل عند تحميل البيانات، ما يحسّن أداء تطبيقك وتجربة المستخدم.