پرس و جوهای DAO ناهمزمان را بنویسید

برای جلوگیری از مسدود شدن رابط کاربری توسط کوئری‌ها، Room از دسترسی به پایگاه داده در thread اصلی پشتیبانی نمی‌کند. این محدودیت به این معنی است که شما باید کوئری‌های DAO خود را ناهمزمان (asynchronous) کنید. کتابخانه Room شامل ادغام با چندین فریم‌ورک برای ارائه اجرای ناهمزمان کوئری است.

پرس‌وجوهای DAO به سه دسته تقسیم می‌شوند:

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

گزینه‌های زبان و چارچوب

روم پشتیبانی یکپارچه‌سازی برای قابلیت همکاری با ویژگی‌ها و کتابخانه‌های خاص زبان را فراهم می‌کند. جدول زیر انواع بازگشتی قابل اجرا را بر اساس نوع پرس‌وجو و چارچوب نشان می‌دهد:

نوع پرس و جو ویژگی‌های زبان کاتلین (بومی) آر ایکس جاوا گواوا چرخه عمر جت‌پک*
نوشتن تک‌مرحله‌ای کوروتین‌ها ( suspend ) Single<T> ، Maybe<T> ، Completable ListenableFuture<T> ناموجود
خواندن تک‌جلسه‌ای کوروتین‌ها ( suspend ) Single<T> ، Maybe<T> ListenableFuture<T> ناموجود
خوانش قابل مشاهده Flow<T> Flowable<T> ، Publisher<T> ، Observable<T> ناموجود LiveData<T>

این راهنما سه روش برای استفاده از این ادغام‌ها برای پیاده‌سازی پرس‌وجوهای ناهمزمان در DAOهای شما را نشان می‌دهد.

کاتلین با Flow و Coroutineها

کاتلین ویژگی‌های داخلی زبان را ارائه می‌دهد که به شما امکان می‌دهد کوئری‌های ناهمزمان را بدون چارچوب‌های شخص ثالث بنویسید:

  • روم مستقیماً از Flow کاتلین برای نوشتن کوئری‌های قابل مشاهده پشتیبانی می‌کند.
  • Room برای ناهمگام کردن کوئری‌های DAO یک‌باره با کوروتین‌های کاتلین، به کلمه کلیدی suspend نیاز دارد.

پشتیبانی از Coroutineها و Flow مستقیماً در هسته‌ی زمان اجرای Room تعبیه شده است، بنابراین به هیچ مصنوعات اضافی نیاز نیست.

RxJava برای کاتلین و جاوا

روم ۳.۰ از ۳ نوع داده‌ی برگشتی در RxJava پشتیبانی می‌کند. برای استفاده از انواع داده‌ی برگشتی در RxJava، باید مبدل‌های نوع داده‌ی برگشتی در RxJava را در پایگاه داده یا DAO خود ثبت کنید:

  1. فایل androidx.room3:room3-rxjava3 را در پیکربندی ساخت خود وارد کنید.
  2. اعلان @Database یا @Dao خود را با @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class) حاشیه‌نویسی کنید.

روم از انواع بازگشتی RxJava 3 زیر پشتیبانی می‌کند:

لایو دیتا و گواوا

روم ۳.۰ از انواع بازگشتی LiveData و Guava ListenableFuture با استفاده از مبدل‌ها پشتیبانی می‌کند:

  • LiveData : آرتیفکت androidx.room3:room3-livedata را اضافه کنید و پایگاه داده یا DAO خود را با @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class) حاشیه‌نویسی کنید.
  • Guava : آرتیفکت androidx.room3:room3-guava را اضافه کنید و پایگاه داده یا DAO خود را با @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class) حاشیه‌نویسی کنید.

نوشتن کوئری‌های تک‌مرحله‌ای ناهمزمان

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

@Dao
interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    suspend fun loadUserById(id: Int): User

    @Query("SELECT * from user WHERE region IN (:regions)")
    suspend fun loadUsersByRegion(regions: List<String>): List<User>
}

نوشتن کوئری‌های قابل مشاهده

پرس‌وجوهای قابل مشاهده، عملیات خواندنی هستند که هر زمان جداول ارجاع‌شده تغییر کنند، مقادیر جدیدی منتشر می‌کنند. برای مثال، می‌توانید از این رفتار برای به‌روزرسانی فهرست نمایش داده‌شده از موارد با تغییر پایگاه داده استفاده کنید. در اینجا چند نمونه از پرس‌وجوهای قابل مشاهده آورده شده است:

@Dao
interface ObservableUserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    fun loadUserById(id: Int): Flow<User>

    @Query("SELECT * from user WHERE region IN (:regions)")
    fun loadUsersByRegion(regions: List<String>): Flow<List<User>>
}

پیگیری نامعتبر بودن پایگاه داده به صورت دستی

وقتی نیاز دارید عملیات پایگاه داده قابل مشاهده را به صورت دستی بسازید، می‌توانید از API createFlow از InvalidationTracker استفاده کنید. این API به شما امکان می‌دهد یک Flow ایجاد کنید که تغییرات در جداول خاص را ردیابی می‌کند و هر زمان که آن جداول تغییر کنند، یک اعلان منتشر می‌کند.

fun getArtistTours(db: RoomDatabase, from: Date, to: Date): Flow<Map<Artist, TourState>> {
    return db.invalidationTracker.createFlow("Artist").map { _ ->
        val artists = artistsDao.getAllArtists()
        val tours = tourService.fetchStates(artists.map { it.id })
        associateTours(artists, tours, from, to)
    }
}

به طور پیش‌فرض، Flow برگردانده شده یک مقدار اولیه شامل تمام جداول ثبت شده را برای شروع استریم منتشر می‌کند. می‌توانید این رفتار را با تنظیم پارامتر emitInitialState روی false غیرفعال کنید.

مبدل‌های نوع بازگشتی DAO سفارشی

برای انواعی که مستقیماً توسط Room یا کتابخانه‌های الحاقی آن پشتیبانی نمی‌شوند، می‌توانید مبدل‌های نوع بازگشتی DAO سفارشی تعریف کنید تا از انواع بازگشتی اضافی پشتیبانی کنند. برای تبدیل نتیجه یک تابع DAO به نوع سفارشی خود، یک تابع مبدل را با @DaoReturnTypeConverter حاشیه‌نویسی کنید.

برای مثال، می‌توانید مبدلی تعریف کنید که از androidx.tracing برای اضافه کردن بخش‌های ردیابی در اطراف اجرای یک کوئری استفاده می‌کند تا کوئری‌های حساس به عملکرد را با قرار دادن اجرا در یک نوع TracedQuery سفارشی، رصد کند:

class TracedQuery<T>(val result: T)

object TracingDaoReturnTypeConverter {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

برای استفاده از مبدل، پایگاه داده یا DAO خود را با @DaoReturnTypeConverters حاشیه‌نویسی کنید:

@Dao
@DaoReturnTypeConverters(TracingDaoReturnTypeConverter::class)
interface MusicDao {
    @Query("SELECT * FROM Song")
    suspend fun getAllSongs(): TracedQuery<List<Song>>
}

مقداردهی اولیه مبدل نوع بازگشتی DAO را کنترل کنید

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

@ProvidedDaoReturnTypeConverter
class TracingDaoReturnTypeConverter(val tracer: Tracer) {
    @DaoReturnTypeConverter([OperationType.READ])
    suspend fun <T> convert(
        rawQuery: RoomRawQuery,
        executeAndConvert: suspend () -> T
    ): TracedQuery<T> {
        val result = tracer.trace("TracedQuery: ${rawQuery.sql}") {
            executeAndConvert()
        }
        return TracedQuery(result)
    }
}

سپس، علاوه بر تعریف کلاس مبدل خود در @DaoReturnTypeConverters ، از تابع RoomDatabase.Builder.addDaoReturnTypeConverter برای ارسال نمونه‌ای از کلاس مبدل خود به سازنده RoomDatabase استفاده کنید:

val db = Room.databaseBuilder<MyDatabase>(applicationContext, "database-name")
    .addDaoReturnTypeConverter(TracingDaoReturnTypeConverter(myLoggerInstance))
    .build()

الزامات عملکرد مبدل

یک تابع @DaoReturnTypeConverter باید چندین الزام را برآورده کند:

  • باید یک پارامتر تابعی به عنوان آخرین آرگومان خود داشته باشد که معمولاً executeAndConvert نام دارد. این پارامتر یک لامبدا suspend است که Room برای اجرای پرس‌وجو و تجزیه نتیجه تولید می‌کند.
    • اگر مبدل نیاز به تبدیل پرس‌وجو، مانند Paging، داشته باشد، لامبدا می‌تواند یک پارامتر RoomRawQuery دریافت کند.
  • می‌تواند به صورت اختیاری پارامترهای زیر را قبل از لامبدا بپذیرد:
    • db: RoomDatabase : به نمونه پایگاه داده دسترسی پیدا می‌کند، که برای بدست آوردن محدوده کوروتین یا انجام عملیات اضافی مفید است.
    • tableNames: Array<String> یا List<String> : نام جداولی را که توسط پرس‌وجو قابل دسترسی هستند، ارائه می‌دهد که برای انواع قابل مشاهده مفید است.
    • rawQuery: RoomRawQuery : نمونه‌ی زمان اجرای کوئری را ارائه می‌دهد.
    • inTransaction: Boolean : نشان می‌دهد که آیا کوئری درون یک تراکنش اجرا می‌شود یا خیر.