ไลบรารีการคงข้อมูลของ Room มีเลเยอร์การแยกแยะระดับบน SQLite เพื่อให้เข้าถึงฐานข้อมูลได้มีประสิทธิภาพมากขึ้น ขณะเดียวกันก็ใช้ความสามารถของ SQLite ได้อย่างเต็มที่ หน้านี้มุ่งเน้นที่การใช้ Room ในโปรเจ็กต์ Kotlin Multiplatform (KMP) ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ Room ได้ที่บันทึกข้อมูลในฐานข้อมูลของเครื่องโดยใช้ Room หรือตัวอย่างอย่างเป็นทางการ
การตั้งค่าทรัพยากร Dependency
เวอร์ชันปัจจุบันของ Room ที่รองรับ KMP คือ 2.7.0-alpha01 ขึ้นไป
หากต้องการตั้งค่า Room ในโปรเจ็กต์ KMP ให้เพิ่มการพึ่งพาสำหรับอาร์ติแฟกต์ในไฟล์ build.gradle.kts
ของโมดูล ดังนี้
androidx.room:room-gradle-plugin
- ปลั๊กอิน Gradle เพื่อกำหนดค่าสคีมา Roomandroidx.room:room-compiler
- โปรแกรมประมวลผล KSP ที่สร้างโค้ดandroidx.room:room-runtime
- ส่วนรันไทม์ของไลบรารีandroidx.sqlite:sqlite-bundled
- (ไม่บังคับ) ไลบรารี SQLite ที่รวมมา
นอกจากนี้ คุณยังต้องกำหนดค่าไดรเวอร์ SQLite ของ Room ด้วย โดยไดรเวอร์เหล่านี้จะแตกต่างกันไปตามแพลตฟอร์มเป้าหมาย ดูคำอธิบายการใช้งานไดรเวอร์ที่พร้อมใช้งานได้ที่หัวข้อการใช้งานไดรเวอร์
ดูข้อมูลการตั้งค่าเพิ่มเติมได้ที่หัวข้อต่อไปนี้
การกําหนดคลาสฐานข้อมูล
คุณต้องสร้างคลาสฐานข้อมูลที่กำกับด้วย @Database
พร้อมกับ DAO และเอนทิตีภายในชุดแหล่งที่มาทั่วไปของโมดูล KMP ที่แชร์ การวางชั้นเรียนเหล่านี้ในแหล่งที่มาทั่วไปจะช่วยให้แชร์ชั้นเรียนในแพลตฟอร์มเป้าหมายทั้งหมดได้
เมื่อคุณประกาศออบเจ็กต์ expect
ที่มีอินเทอร์เฟซ RoomDatabaseConstructor
คอมไพเลอร์ Room จะสร้างการใช้งาน actual
Android Studio อาจแสดงคำเตือน
"Expected object 'AppDatabaseConstructor' has no actual declaration in
module"
คุณสามารถปิดการแสดงคำเตือนได้ด้วย @Suppress("NO_ACTUAL_FOR_EXPECT")
// shared/src/commonMain/kotlin/Database.kt
@Database(entities = [TodoEntity::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun getDao(): TodoDao
}
// The Room compiler generates the `actual` implementations.
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
@Dao
interface TodoDao {
@Insert
suspend fun insert(item: TodoEntity)
@Query("SELECT count(*) FROM TodoEntity")
suspend fun count(): Int
@Query("SELECT * FROM TodoEntity")
fun getAllAsFlow(): Flow<List<TodoEntity>>
}
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
โปรดทราบว่าคุณอาจใช้การประกาศจริง / คาดหวังเพื่อสร้างการใช้งาน Room สำหรับแพลตฟอร์มใดแพลตฟอร์มหนึ่งโดยเฉพาะ เช่น คุณสามารถเพิ่ม DAO สำหรับแพลตฟอร์มที่เฉพาะเจาะจงซึ่งกำหนดไว้ในโค้ดทั่วไปโดยใช้ expect
จากนั้นระบุคำจำกัดความ actual
ด้วยการค้นหาเพิ่มเติมในชุดแหล่งที่มาสำหรับแพลตฟอร์มที่เฉพาะเจาะจง
การสร้างเครื่องมือสร้างฐานข้อมูล
คุณต้องกำหนดเครื่องมือสร้างฐานข้อมูลเพื่อสร้างอินสแตนซ์ Room ในแต่ละแพลตฟอร์ม นี่เป็นส่วนที่เดียวของ API ที่ต้องอยู่ในชุดแหล่งที่มาเฉพาะแพลตฟอร์มเนื่องจากความแตกต่างของ File System API ตัวอย่างเช่น ใน Android ปกติแล้วตำแหน่งฐานข้อมูลจะได้รับผ่าน Context.getDatabasePath()
API ส่วนใน iOS ตำแหน่งฐานข้อมูลจะได้รับผ่าน NSFileManager
Android
หากต้องการสร้างอินสแตนซ์ฐานข้อมูล ให้ระบุบริบทพร้อมกับเส้นทางฐานข้อมูล
// shared/src/androidMain/kotlin/Database.kt
fun getDatabaseBuilder(ctx: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = ctx.applicationContext
val dbFile = appContext.getDatabasePath("my_room.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
iOS
หากต้องการสร้างอินสแตนซ์ฐานข้อมูล ให้ระบุเส้นทางฐานข้อมูลโดยใช้ NSFileManager
ซึ่งปกติจะอยู่ใน NSDocumentDirectory
// shared/src/iosMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFilePath = documentDirectory() + "/my_room.db"
return Room.databaseBuilder<AppDatabase>(
name = dbFilePath,
)
}
private fun documentDirectory(): String {
val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
return requireNotNull(documentDirectory?.path)
}
JVM (เดสก์ท็อป)
หากต้องการสร้างอินสแตนซ์ฐานข้อมูล ให้ระบุเส้นทางฐานข้อมูลโดยใช้ Java หรือ Kotlin API
// shared/src/jvmMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
การลดขนาดและการสร้างความสับสน
หากโปรเจ็กต์ได้รับการย่อขนาดหรือสร้างความสับสน คุณต้องใส่กฎ ProGuard ต่อไปนี้เพื่อให้ Room พบการใช้งานที่สร้างขึ้นของคําจํากัดความฐานข้อมูล
-keep class * extends androidx.room.RoomDatabase { <init>(); }
การสร้างอินสแตนซ์ฐานข้อมูล
เมื่อได้รับ RoomDatabase.Builder
จากตัวสร้างเฉพาะแพลตฟอร์มแล้ว คุณจะกำหนดค่าฐานข้อมูล Room ที่เหลือได้ในโค้ดทั่วไป พร้อมกับการสร้างอินสแตนซ์ฐานข้อมูลจริง
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.addMigrations(MIGRATIONS)
.fallbackToDestructiveMigrationOnDowngrade()
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
การเลือก SQLiteDriver
ข้อมูลโค้ดก่อนหน้าใช้ BundledSQLiteDriver
นี่คือไดรเวอร์ที่แนะนำซึ่งมี SQLite ที่คอมไพล์จากซอร์สโค้ด ซึ่งให้ SQLite เวอร์ชันล่าสุดที่สอดคล้องกันมากที่สุดในทุกแพลตฟอร์ม หากต้องการใช้ SQLite ที่ระบบปฏิบัติการให้มา ให้ใช้ setDriver
API ในชุดแหล่งที่มาเฉพาะแพลตฟอร์มที่ระบุไดรเวอร์เฉพาะแพลตฟอร์ม สําหรับ Android คุณสามารถใช้
AndroidSQLiteDriver
ส่วนสําหรับ iOS คุณสามารถใช้ NativeSQLiteDriver
หากต้องการใช้ NativeSQLiteDriver
คุณต้องระบุตัวเลือก linker เพื่อให้แอป iOS ลิงก์กับ SQLite ของระบบแบบไดนามิก
// shared/build.gradle.kts
kotlin {
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "TodoApp"
isStatic = true
// Required when using NativeSQLiteDriver
linkerOpts.add("-lsqlite3")
}
}
}
ความแตกต่าง
Room เดิมพัฒนาขึ้นเป็นไลบรารี Android และต่อมาได้ย้ายข้อมูลไปยัง KMP โดยมุ่งเน้นที่ความเข้ากันได้ของ API Room เวอร์ชัน KMP จะแตกต่างกันไปบ้างระหว่างแพลตฟอร์มต่างๆ และเวอร์ชันสำหรับ Android โดยเฉพาะ ความแตกต่างเหล่านี้จะแสดงรายการและอธิบายไว้ดังนี้
การบล็อกฟังก์ชัน DAO
เมื่อใช้ Room สําหรับ KMP ฟังก์ชัน DAO ทั้งหมดที่คอมไพล์สําหรับแพลตฟอร์มที่ไม่ใช่ Android ต้องเป็นฟังก์ชัน suspend
ยกเว้นประเภทผลลัพธ์แบบรีแอ็กทีฟ เช่น Flow
// shared/src/commonMain/kotlin/MultiplatformDao.kt
@Dao
interface MultiplatformDao {
// ERROR: Blocking function not valid for non-Android targets
@Query("SELECT * FROM Entity")
fun blockingQuery(): List<Entity>
// OK
@Query("SELECT * FROM Entity")
suspend fun query(): List<Entity>
// OK
@Query("SELECT * FROM Entity")
fun queryFlow(): Flow<List<Entity>>
// ERROR: Blocking function not valid for non-Android targets
@Transaction
fun blockingTransaction() { // … }
// OK
@Transaction
suspend fun transaction() { // … }
}
Room ใช้ประโยชน์จากไลบรารี kotlinx.coroutines
แบบแอซิงโครนัสที่เต็มไปด้วยฟีเจอร์ซึ่ง Kotlin มีให้บริการสำหรับแพลตฟอร์มหลายแพลตฟอร์ม ระบบจะบังคับใช้suspend
ฟังก์ชันสําหรับ DAO ที่คอมไพล์ในโปรเจ็กต์ KMP เพื่อให้ฟังก์ชันทํางานได้อย่างมีประสิทธิภาพสูงสุด ยกเว้น DAO สําหรับ Android โดยเฉพาะเพื่อรักษาความเข้ากันได้ย้อนหลังกับฐานโค้ดที่มีอยู่
ความแตกต่างของฟีเจอร์กับ KMP
ส่วนนี้จะอธิบายความแตกต่างของฟีเจอร์ระหว่าง Room เวอร์ชันแพลตฟอร์ม KMP กับ Android
ฟังก์ชัน DAO ของ @RawQuery
ฟังก์ชันที่มีคำอธิบายประกอบ @RawQuery
ซึ่งคอมไพล์สำหรับแพลตฟอร์มที่ไม่ใช่ Android จะต้องมีการประกาศพารามิเตอร์ประเภท RoomRawQuery
แทน SupportSQLiteQuery
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query RoomRawQuery): List<TodoEntity>
}
จากนั้นจะใช้ RoomRawQuery
เพื่อสร้างการค้นหาที่รันไทม์ได้ ดังนี้
suspend fun getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?"
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todosDao.getTodos(query)
}
Callback ของคําค้นหา
API ต่อไปนี้สําหรับการกําหนดค่าการเรียกกลับการค้นหาไม่พร้อมใช้งานในเวอร์ชันทั่วไป และจึงไม่พร้อมใช้งานในแพลตฟอร์มอื่นๆ นอกเหนือจาก Android
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
เราตั้งใจที่จะเพิ่มการรองรับการเรียกกลับคําค้นหาใน Room เวอร์ชันในอนาคต
API เพื่อกําหนดค่า RoomDatabase
ด้วยคอลแบ็กการค้นหา
RoomDatabase.Builder.setQueryCallback
พร้อมกับอินเทอร์เฟซคอลแบ็ก
RoomDatabase.QueryCallback
นั้นไม่พร้อมใช้งานโดยทั่วไป จึงใช้งานไม่ได้ในแพลตฟอร์มอื่นๆ นอกเหนือจาก Android
ฐานข้อมูลที่ปิดอัตโนมัติ
API เพื่อเปิดใช้การปิดอัตโนมัติหลังจากหมดเวลา RoomDatabase.Builder.setAutoCloseTimeout
มีให้บริการใน Android เท่านั้น และไม่มีให้บริการในแพลตฟอร์มอื่นๆ
ฐานข้อมูลก่อนแพ็กเกจ
API ต่อไปนี้สำหรับสร้าง RoomDatabase
โดยใช้ฐานข้อมูลที่มีอยู่ (เช่น ฐานข้อมูลที่แพ็กเกจไว้ล่วงหน้า) จะใช้ไม่ได้ในแพลตฟอร์มทั่วไป และจึงไม่พร้อมใช้งานในแพลตฟอร์มอื่นๆ นอกเหนือจาก Android API เหล่านี้ได้แก่
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
เราตั้งใจที่จะเพิ่มการรองรับฐานข้อมูลที่แพ็กเกจไว้ล่วงหน้าใน Room เวอร์ชันในอนาคต
การทำให้ใช้งานไม่ได้หลายอินสแตนซ์
API เพื่อเปิดใช้การลบล้างอินสแตนซ์หลายรายการ RoomDatabase.Builder.enableMultiInstanceInvalidation
พร้อมใช้งานใน Android เท่านั้น และไม่พร้อมใช้งานในแพลตฟอร์มทั่วไปหรือแพลตฟอร์มอื่นๆ