Ghi các truy vấn DAO không đồng bộ

Để tránh trường hợp các truy vấn chặn giao diện người dùng, Room không hỗ trợ quyền truy cập cơ sở dữ liệu trên luồng chính. Hạn chế này có nghĩa là bạn phải tạo truy vấn DAO ở chế độ không đồng bộ. Thư viện Room bao gồm quá trình tích hợp với nhiều khung để thực thi truy vấn không đồng bộ.

Chúng tôi phân loại truy vấn DAO thành 3 danh mục sau:

  • Truy vấn ghi một lần: chèn, cập nhật hoặc xoá dữ liệu trong cơ sở dữ liệu.
  • Truy vấn đọc một lần: chỉ đọc dữ liệu từ cơ sở dữ liệu một lần và trả về kết quả kèm theo thông tin tổng quan nhanh về cơ sở dữ liệu tại thời điểm đó.
  • Truy vấn đọc có thể quan sát: đọc dữ liệu từ cơ sở dữ liệu mỗi khi bảng cơ sở dữ liệu cơ bản thay đổi và tạo ra giá trị mới để phản ánh những thay đổi đó.

Tuỳ chọn khung và ngôn ngữ

Room hỗ trợ quá trình tích hợp để có khả năng tương tác với các tính năng và thư viện ngôn ngữ cụ thể. Bảng sau đây trình bày các loại dữ liệu trả về (nếu có) dựa trên loại truy vấn và khung:

Loại truy vấn Tính năng ngôn ngữ Kotlin (Native) RxJava Màu ổi Jetpack Lifecycle*
Ghi một lần Coroutine (suspend) Single<T>, Maybe<T>, Completable ListenableFuture<T> Không có
Đọc một lần Coroutine (suspend) Single<T>, Maybe<T> ListenableFuture<T> Không có
Đọc có thể quan sát Flow<T> Flowable<T>, Publisher<T>, Observable<T> Không áp dụng LiveData<T>

Hướng dẫn này minh hoạ 3 cách sử dụng những quy trình tích hợp này để triển khai các truy vấn không đồng bộ trong DAO của bạn.

Kotlin với Flow và coroutine

Kotlin cung cấp các tính năng ngôn ngữ tích hợp cho phép bạn viết các truy vấn không đồng bộ mà không cần khung của bên thứ ba:

  • Room hỗ trợ trực tiếp Flow của Kotlin để ghi các truy vấn có thể quan sát.
  • Room yêu cầu từ khoá suspend để tạo truy vấn DAO một lần ở chế độ không đồng bộ bằng coroutine của Kotlin.

Tính năng hỗ trợ Coroutine và Flow được tích hợp trực tiếp vào thời gian chạy Room cốt lõi, nên bạn không cần thêm cấu phần phần mềm nào.

RxJava cho Kotlin và Java

Room 3.0 hỗ trợ các kiểu dữ liệu trả về của RxJava 3. Để sử dụng các kiểu dữ liệu trả về RxJava, bạn phải đăng ký trình chuyển đổi kiểu dữ liệu trả về RxJava trong cơ sở dữ liệu hoặc DAO:

  1. Đưa cấu phần phần mềm androidx.room3:room3-rxjava3 vào cấu hình bản dựng.
  2. Chú giải khai báo @Database hoặc @Dao bằng @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class).

Room hỗ trợ các kiểu dữ liệu trả về của RxJava 3 sau đây:

LiveData và Guava

Room 3.0 hỗ trợ các kiểu dữ liệu trả về LiveData và Guava ListenableFuture bằng cách sử dụng các trình chuyển đổi:

  • LiveData: Thêm cấu phần phần mềm androidx.room3:room3-livedata và chú thích cơ sở dữ liệu hoặc DAO bằng @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class).
  • Guava: Thêm cấu phần phần mềm androidx.room3:room3-guava và chú thích cơ sở dữ liệu hoặc DAO của bạn bằng @DaoReturnTypeConverters(GuavaDaoReturnTypeConverter::class).

Ghi các truy vấn một lần không đồng bộ

Truy vấn một lần là các thao tác đối với cơ sở dữ liệu chỉ chạy một lần và lấy thông tin tổng quan nhanh về dữ liệu tại thời điểm thực thi. Dưới đây là một số ví dụ về truy vấn một lần không đồng bộ:

@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>
}

Ghi các truy vấn có thể quan sát

Truy vấn có thể quan sát là các thao tác đọc tạo ra giá trị mới mỗi khi các bảng được tham chiếu thay đổi. Ví dụ: bạn có thể sử dụng hành vi này để luôn cập nhật danh sách các mục hiển thị khi cơ sở dữ liệu thay đổi. Dưới đây là một số ví dụ về truy vấn có thể quan sát:

@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>>
}

Theo dõi việc vô hiệu hoá cơ sở dữ liệu theo cách thủ công

Khi cần tạo các thao tác cơ sở dữ liệu có thể ghi nhận được theo cách thủ công, bạn có thể sử dụng API createFlow của InvalidationTracker. API này cho phép bạn tạo một Flow theo dõi các sửa đổi đối với những bảng cụ thể và phát ra thông báo bất cứ khi nào các bảng đó thay đổi.

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)
    }
}

Theo mặc định, Flow được trả về sẽ phát ra một giá trị ban đầu chứa tất cả các bảng đã đăng ký để bắt đầu luồng. Bạn có thể tắt hành vi này bằng cách đặt tham số emitInitialState thành false.

Trình chuyển đổi kiểu dữ liệu trả về DAO tuỳ chỉnh

Đối với các loại không được Room hoặc các thư viện tiện ích của Room hỗ trợ trực tiếp, bạn có thể xác định trình chuyển đổi kiểu dữ liệu trả về DAO tuỳ chỉnh để hỗ trợ các kiểu dữ liệu trả về bổ sung. Để chuyển đổi kết quả của một hàm DAO thành loại tuỳ chỉnh, hãy chú giải một hàm chuyển đổi bằng @DaoReturnTypeConverter.

Ví dụ: bạn có thể xác định một trình chuyển đổi sử dụng androidx.tracing để thêm các phần theo dõi xung quanh quá trình thực thi một truy vấn nhằm theo dõi các truy vấn nhạy cảm về hiệu suất bằng cách bao bọc quá trình thực thi trong một loại TracedQuery tuỳ chỉnh:

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)
    }
}

Để sử dụng trình chuyển đổi, hãy chú giải cơ sở dữ liệu hoặc DAO bằng @DaoReturnTypeConverters:

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

Kiểm soát quá trình khởi chạy trình chuyển đổi loại dữ liệu trả về DAO

Thông thường, Room xử lý việc tạo bản sao của trình chuyển đổi kiểu dữ liệu trả về DAO. Tuy nhiên, nếu phải truyền các phần phụ thuộc bổ sung vào các lớp trình chuyển đổi, ứng dụng của bạn phải kiểm soát trực tiếp quá trình khởi chạy các lớp đó. Nếu có, hãy chú giải lớp trình chuyển đổi của bạn bằng @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)
    }
}

Sau đó, ngoài việc khai báo lớp trình chuyển đổi của bạn trong @DaoReturnTypeConverters, hãy sử dụng hàm RoomDatabase.Builder.addDaoReturnTypeConverter để truyền một thực thể của lớp trình chuyển đổi đến trình tạo RoomDatabase:

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

Yêu cầu về chức năng của bộ chuyển đổi

Hàm @DaoReturnTypeConverter phải đáp ứng một số yêu cầu:

  • Hàm này phải có một tham số hàm làm đối số cuối cùng, thường được đặt tên là executeAndConvert. Tham số này là một lambda suspend mà Room tạo ra để thực thi truy vấn và phân tích cú pháp kết quả.
    • Nếu bộ chuyển đổi cần chuyển đổi truy vấn, chẳng hạn như Phân trang, thì lambda có thể lấy tham số RoomRawQuery.
  • Bạn có thể tuỳ ý chấp nhận các tham số sau trước lambda:
    • db: RoomDatabase: Truy cập vào thực thể cơ sở dữ liệu, rất hữu ích để lấy phạm vi coroutine hoặc thực hiện các thao tác bổ sung.
    • tableNames: Array<String> hoặc List<String>: Cung cấp tên của các bảng mà truy vấn truy cập, rất hữu ích cho các loại có thể quan sát.
    • rawQuery: RoomRawQuery: Cung cấp phiên bản thời gian chạy của truy vấn.
    • inTransaction: Boolean: Cho biết liệu truy vấn có đang thực thi trong một giao dịch hay không.