ارجاع داده های پیچیده با استفاده از Room

Room عملکردی را برای تبدیل بین انواع اولیه و جعبه‌ای ارائه می‌کند، اما اجازه ارجاع به شی بین موجودیت‌ها را نمی‌دهد. این سند نحوه استفاده از مبدل‌های نوع و اینکه چرا Room از ارجاعات شی پشتیبانی نمی‌کند، توضیح می‌دهد.

از مبدل های نوع استفاده کنید

گاهی اوقات، شما نیاز دارید که برنامه خود یک نوع داده سفارشی را در یک ستون پایگاه داده ذخیره کند. شما از انواع سفارشی با ارائه مبدل‌های نوع پشتیبانی می‌کنید، که روش‌هایی هستند که به اتاق می‌گویند چگونه انواع سفارشی را به و از انواع شناخته‌شده‌ای که Room می‌تواند ادامه دهد، تبدیل کند. شما مبدل های نوع را با استفاده از حاشیه نویسی @TypeConverter شناسایی می کنید.

فرض کنید باید نمونه‌هایی از Date در پایگاه داده اتاق خود نگه دارید. Room نمی داند چگونه اشیاء Date حفظ کند، بنابراین باید مبدل های نوع را تعریف کنید:

کاتلین

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

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

جاوا

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 استفاده کند.

سپس، حاشیه‌نویسی @TypeConverters به کلاس AppDatabase اضافه می‌کنید تا Room از کلاس مبدلی که تعریف کرده‌اید مطلع شود:

کاتلین

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

جاوا

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

با تعریف این مبدل‌های نوع، می‌توانید از نوع سفارشی خود در موجودیت‌ها و DAO‌های خود استفاده کنید، همانطور که از انواع اولیه استفاده می‌کنید:

کاتلین

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

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

جاوا

@Entity
public class User {
  private Date birthday;
}

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

در این مثال، Room می‌تواند از مبدل نوع تعریف‌شده در همه جا استفاده کند، زیرا شما AppDatabase با @TypeConverters حاشیه‌نویسی کرده‌اید. با این حال، می‌توانید با حاشیه‌نویسی کلاس‌های @Entity یا @Dao خود با @TypeConverters ، مبدل‌های نوع دامنه را به موجودیت‌ها یا DAO‌های خاص تبدیل کنید.

مقداردهی اولیه مبدل نوع کنترل

معمولاً اتاق نمونه‌سازی مبدل‌های نوع را برای شما انجام می‌دهد. با این حال، گاهی اوقات ممکن است لازم باشد وابستگی‌های اضافی را به کلاس‌های مبدل نوع خود منتقل کنید، به این معنی که به برنامه خود نیاز دارید تا مستقیماً مقداردهی اولیه مبدل‌های نوع خود را کنترل کند. در این صورت، کلاس مبدل خود را با @ProvidedTypeConverter حاشیه نویسی کنید:

کاتلین

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

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

جاوا

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

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

سپس، علاوه بر اعلام کلاس مبدل خود در @TypeConverters ، از متد RoomDatabase.Builder.addTypeConverter() برای ارسال نمونه ای از کلاس مبدل خود به سازنده RoomDatabase استفاده کنید:

کاتلین

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

جاوا

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

درک کنید که چرا Room به اشیاء ارجاع نمی دهد

نکته کلیدی: Room ارجاع شی بین کلاس های موجودیت را ممنوع می کند. در عوض، باید صریحاً داده‌هایی را که برنامه شما به آن نیاز دارد درخواست کنید.

نگاشت روابط از یک پایگاه داده به مدل شی مربوطه یک روش معمول است و در سمت سرور بسیار خوب عمل می کند. حتی زمانی که برنامه فیلدها را با دسترسی به آنها بارگیری می کند، سرور همچنان عملکرد خوبی دارد.

با این حال، در سمت کلاینت، این نوع بارگذاری تنبل امکان پذیر نیست، زیرا معمولاً در رشته UI رخ می دهد، و جستجوی اطلاعات روی دیسک در رشته UI مشکلات عملکرد قابل توجهی ایجاد می کند. رشته UI معمولاً حدود 16 میلی‌ثانیه برای محاسبه و ترسیم طرح‌بندی به‌روز شده یک فعالیت دارد، بنابراین حتی اگر یک پرس‌وجو فقط 5 میلی‌ثانیه طول بکشد، باز هم احتمال دارد برنامه شما برای ترسیم فریم زمان تمام شود و اشکالات بصری قابل توجهی ایجاد کند. اگر تراکنش جداگانه ای به صورت موازی در حال اجرا باشد، یا اگر دستگاه در حال اجرای سایر وظایف فشرده دیسک باشد، این پرس و جو ممکن است زمان بیشتری را صرف کند. با این حال، اگر از بارگذاری تنبل استفاده نمی کنید، برنامه شما داده های بیشتری از نیاز خود دریافت می کند و مشکلاتی در مصرف حافظه ایجاد می کند.

نگاشت‌های رابطه‌ای شی معمولاً این تصمیم را به توسعه‌دهندگان واگذار می‌کنند تا بتوانند بهترین کار را برای موارد استفاده برنامه خود انجام دهند. توسعه دهندگان معمولاً تصمیم می گیرند مدل را بین برنامه خود و رابط کاربری به اشتراک بگذارند. با این حال، این راه حل به خوبی مقیاس بندی نمی شود، زیرا با تغییر رابط کاربری در طول زمان، مدل مشترک مشکلاتی را ایجاد می کند که پیش بینی و اشکال زدایی آن برای توسعه دهندگان دشوار است.

به عنوان مثال، رابط کاربری را در نظر بگیرید که فهرستی از اشیاء Book را بارگیری می‌کند و هر کتاب دارای یک شیء Author است. ممکن است ابتدا درخواست های خود را طوری طراحی کنید که از بارگذاری تنبل استفاده کنید تا نمونه هایی از Book نویسنده را بازیابی کند. اولین بازیابی فیلد author ، پایگاه داده را پرس و جو می کند. مدتی بعد، متوجه می‌شوید که باید نام نویسنده را در رابط کاربری برنامه خود نیز نمایش دهید. همانطور که در قطعه کد زیر نشان داده شده است، می توانید به راحتی به این نام دسترسی پیدا کنید:

کاتلین

authorNameTextView.text = book.author.name

جاوا

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

با این حال، این تغییر به ظاهر بی‌گناه باعث می‌شود که جدول Author در موضوع اصلی مورد پرسش قرار گیرد.

اگر اطلاعات نویسنده را زودتر از موعد جستجو کنید، تغییر نحوه بارگیری داده ها در صورتی که دیگر به آن داده ها نیاز نداشته باشید دشوار می شود. به عنوان مثال، اگر رابط کاربری برنامه شما دیگر نیازی به نمایش اطلاعات Author نداشته باشد، برنامه شما به طور موثر داده هایی را که دیگر نمایش نمی دهد بارگیری می کند و فضای ارزشمند حافظه را هدر می دهد. اگر کلاس Author به جدول دیگری مانند Books ارجاع دهد، کارایی برنامه شما حتی بیشتر کاهش می یابد.

برای ارجاع همزمان چندین موجودیت با استفاده از Room، به جای آن یک POJO ایجاد می‌کنید که حاوی هر موجودیت است، سپس یک کوئری می‌نویسید که به جداول مربوطه می‌پیوندد. این مدل به خوبی ساختار یافته، همراه با قابلیت‌های قوی اعتبارسنجی پرس و جوی Room، به برنامه شما اجازه می‌دهد منابع کمتری را هنگام بارگیری داده مصرف کند و عملکرد برنامه و تجربه کاربری شما را بهبود بخشد.