เขียนคำค้นหา DAO แบบไม่พร้อมกัน

Room ไม่รองรับการเข้าถึงฐานข้อมูลในเทรดหลักเพื่อป้องกันไม่ให้คำค้นหาบล็อก UI ข้อจำกัดนี้หมายความว่าคุณต้องทำให้คำค้นหา DAO เป็นแบบอะซิงโครนัส ไลบรารี Room มีการผสานรวมกับเฟรมเวิร์กหลายรายการเพื่อรองรับการดำเนินการคำค้นหาแบบอะซิงโครนัส

คำค้นหา DAO แบ่งออกเป็น 3 หมวดหมู่ดังนี้

  • คำค้นหา การเขียนแบบครั้งเดียว ที่แทรก อัปเดต หรือลบข้อมูลในฐานข้อมูล
  • คำค้นหา การอ่านแบบครั้งเดียว ที่อ่านข้อมูลจากฐานข้อมูลเพียงครั้งเดียวและแสดงผลลัพธ์พร้อมสแนปชอตของฐานข้อมูล ณ เวลานั้น
  • คำค้นหา การอ่านแบบสังเกตได้ ที่อ่านข้อมูลจากฐานข้อมูลทุกครั้งที่ตารางฐานข้อมูลพื้นฐานมีการเปลี่ยนแปลง และส่งค่าใหม่เพื่อแสดงการเปลี่ยนแปลงเหล่านั้น

ตัวเลือกภาษาและเฟรมเวิร์ก

Room รองรับการผสานรวมเพื่อการทำงานร่วมกันกับฟีเจอร์และไลบรารีภาษาที่เฉพาะเจาะจง ตารางต่อไปนี้แสดงประเภทการแสดงผลที่ใช้ได้ตามประเภทคำค้นหาและเฟรมเวิร์ก

ประเภทคำค้นหา ฟีเจอร์ภาษา Kotlin (เนทีฟ) RxJava กวาวา Jetpack Lifecycle*
การเขียนแบบครั้งเดียว โครูทีน (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>

คู่มือนี้แสดง 3 วิธีในการใช้การผสานรวมเหล่านี้เพื่อใช้คำค้นหาแบบอะซิงโครนัสใน DAO

Kotlin พร้อม Flow และโครูทีน

Kotlin มีฟีเจอร์ภาษาในตัวที่ช่วยให้คุณเขียนคำค้นหาแบบอะซิงโครนัสได้โดยไม่ต้องใช้เฟรมเวิร์กของบุคคลที่สาม

  • Room รองรับ Flow ของ Kotlin โดยตรง เพื่อเขียนคำค้นหาแบบสังเกตได้
  • Room กำหนดให้ใช้คีย์เวิร์ด suspend เพื่อทำให้คำค้นหา DAO แบบครั้งเดียว เป็นแบบอะซิงโครนัสด้วย โครูทีนของ Kotlin

การรองรับโครูทีนและ Flow มีอยู่ในรันไทม์หลักของ Room โดยตรง จึงไม่จำเป็นต้องมีอาร์ติแฟกต์เพิ่มเติม

RxJava สำหรับ Kotlin และ Java

Room 3.0 รองรับประเภทการแสดงผล RxJava 3 หากต้องการใช้ประเภทการแสดงผล RxJava คุณต้องลงทะเบียนตัวแปลงประเภทการแสดงผล RxJava ในฐานข้อมูลหรือ DAO โดยทำดังนี้

  1. รวมอาร์ติแฟกต์ androidx.room3:room3-rxjava3 ไว้ในการกำหนดค่าบิลด์
  2. ใส่คำอธิบายประกอบ @Database หรือการประกาศ @Dao ด้วย @DaoReturnTypeConverters(RxDaoReturnTypeConverters::class)

Room รองรับประเภทการแสดงผล RxJava 3 ต่อไปนี้

LiveData และกวาวา

Room 3.0 รองรับประเภทการแสดงผล LiveData และ ListenableFuture ของกวาวาโดยใช้ตัวแปลง

  • LiveData: รวมอาร์ติแฟกต์ androidx.room3:room3-livedata และ ใส่คำอธิบายประกอบฐานข้อมูลหรือ DAO ด้วย @DaoReturnTypeConverters(LiveDataDaoReturnTypeConverter::class)
  • กวาวา: รวมอาร์ติแฟกต์ 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>>
}

ติดตามการทำให้ฐานข้อมูลไม่ถูกต้องด้วยตนเอง

เมื่อคุณต้องสร้างการดำเนินการฐานข้อมูลแบบสังเกตได้ด้วยตนเอง คุณสามารถใช้ createFlow API ของ 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 สร้างขึ้นเพื่อดำเนินการคำค้นหาและแยกวิเคราะห์ผลลัพธ์
    • หากตัวแปลงต้องแปลงคำค้นหา เช่น การแบ่งหน้า แลมบ์ดาสามารถใช้พารามิเตอร์ RoomRawQuery ได้
  • สามารถยอมรับพารามิเตอร์ต่อไปนี้ก่อนแลมบ์ดาได้ (ไม่บังคับ)
    • db: RoomDatabase: เข้าถึงอินสแตนซ์ฐานข้อมูล ซึ่งมีประโยชน์สำหรับการรับขอบเขตโครูทีนหรือการดำเนินการเพิ่มเติม
    • tableNames: Array<String> หรือ List<String>: แสดงชื่อตารางที่คำค้นหาเข้าถึง ซึ่งมีประโยชน์สำหรับประเภทที่สังเกตได้
    • rawQuery: RoomRawQuery: แสดงอินสแตนซ์รันไทม์ของคำค้นหา
    • inTransaction: Boolean: บ่งบอกว่าคำค้นหากำลังดำเนินการภายในธุรกรรมหรือไม่