Room 持續性資料庫是 SQLite 提供的抽象層,讓您在充分運用 SQLiteg 的強大效能時,也能順暢且穩定地存取資料庫。本頁重點介紹如何在 Kotlin Multiplatform (KMP) 專案中使用 Room。如要進一步瞭解如何使用 Room,請參閱「使用 Room 將資料儲存在本機資料庫」或官方範例。
設定依附元件
目前支援 KMP 的 Room 版本為 2.7.0-alpha01 以上版本。
如要在 KMP 專案中設定 Room,請在模組的 build.gradle.kts
檔案中新增構件的依附元件:
androidx.room:room-gradle-plugin
- 用於設定 Room 結構定義的 Gradle 外掛程式androidx.room:room-compiler
:產生程式的 KSP 處理器androidx.room:room-runtime
- 程式庫的執行階段部分androidx.sqlite:sqlite-bundled
- (選用) 內含的 SQLite 程式庫
此外,您還需要設定 Room 的 SQLite 驅動程式。這些驅動程式會因目標平台而異。如要瞭解可用的驅動程式實作方式,請參閱「驅動程式實作」。
如需其他設定資訊,請參閱下列資源:
定義資料庫類別
您需要建立使用 @Database
註解的資料庫類別,以及共用 KMP 模組的通用來源集內的 DAO 和實體。將這些類別放在通用來源中,即可在所有目標平台上共用這些類別。
使用介面 RoomDatabaseConstructor
宣告 expect
物件時,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 實作。舉例來說,您可以使用 expect
在一般程式碼中定義特定平台的 DAO,然後在特定平台的來源集合中,使用其他查詢指定 actual
定義。
建立資料庫建構工具
您需要定義資料庫建構工具,才能在各個平台上將 Room 例項化。由於檔案系統 API 有所不同,因此這是 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
,您必須提供連結器選項,讓 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 for KMP 時,為非 Android 平台編譯的所有 DAO 函式都必須是 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 可從 Kotlin 為多個平台提供的功能豐富的非同步 kotlinx.coroutines
程式庫中受益。為提供最佳功能,系統會針對在 KMP 專案中編譯的 DAO 強制執行 suspend
函式,但 Android 專屬 DAO 除外,以便維持與現有程式碼集的回溯相容性。
與 KMP 的功能差異
本節將說明 KMP 與 Android 平台版本的 Room 之間的功能差異。
@RawQuery DAO 函式
針對非 Android 平台編譯的函式,如果附有 @RawQuery
註解,就必須宣告 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)
}
查詢回呼
下列用於設定查詢回呼的 API 不適用於 common,因此不適用於 Android 以外的平台。
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
我們預計在日後推出的 Room 版本中,新增對查詢回呼的支援。
用於設定 RoomDatabase
的 API 與查詢回呼 RoomDatabase.Builder.setQueryCallback
和回呼介面 RoomDatabase.QueryCallback
皆不屬於常見類別,因此無法在 Android 以外的其他平台上使用。
自動關閉資料庫
RoomDatabase.Builder.setAutoCloseTimeout
是用於在逾時後啟用自動關閉功能的 API,僅適用於 Android,無法在其他平台上使用。
預先封裝資料庫
以下 API 可用於使用現有資料庫 (即預先封裝的資料庫) 建立 RoomDatabase
,但不包含在 common 中,因此無法在 Android 以外的其他平台上使用。這些 API 如下:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
我們預計在日後推出的 Room 版本中,新增對預先封裝資料庫的支援。
多實體失效
啟用多個例項無效化的 API RoomDatabase.Builder.enableMultiInstanceInvalidation
僅適用於 Android,不適用於常見或其他平台。