Room 持續性資料庫透過 SQLite 提供抽象層,可讓資料庫存取更穩固,同時充分發揮 SQLite 的效用。本頁著重於在 Kotlin Multiplatform (KMP) 專案中使用 Room。如要進一步瞭解如何使用 Room,請參閱「使用 Room 將資料儲存在本機資料庫」或官方範例。
設定依附元件
如要在 KMP 專案中設定 Room,請在 KMP 模組的 build.gradle.kts
檔案中新增構件的依附元件。
在 libs.versions.toml
檔案中定義依附元件:
[versions]
room = "2.7.2"
sqlite = "2.5.2"
ksp = "<kotlinCompatibleKspVersion>"
[libraries]
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
# Optional SQLite Wrapper available in version 2.8.0 and higher
androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", version.ref = "room" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
androidx-room = { id = "androidx.room", version.ref = "room" }
新增 Room Gradle 外掛程式,設定 Room 結構定義和 KSP 外掛程式
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.androidx.room)
}
新增 Room 執行階段依附元件和隨附的 SQLite 程式庫:
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
}
// Optional when using Room SQLite Wrapper
androidMain.dependencies {
implementation(libs.androidx.room.sqlite.wrapper)
}
將 KSP 依附元件新增至根 dependencies
區塊。請注意,您必須新增應用程式使用的所有目標。詳情請參閱「KSP with Kotlin Multiplatform」。
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
add("kspIosX64", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
// Add any other platform target you use in your project, for example kspDesktop
}
定義 Room 結構定義目錄。詳情請參閱「使用 Room Gradle 外掛程式設定結構定義位置」。
room {
schemaDirectory("$projectDir/schemas")
}
定義資料庫類別
您需要在共用 KMP 模組的通用來源集中,建立加上 @Database
註解的資料庫類別,以及 DAO 和實體。將這些類別放在通用來源中,即可在所有目標平台之間共用。
// 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("KotlinNoActualForExpect")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
使用 RoomDatabaseConstructor
介面宣告 expect
物件時,Room 編譯器會產生 actual
實作項目。Android Studio 可能會發出以下警告,但您可以使用 @Suppress("KotlinNoActualForExpect")
抑制警告:
Expected object 'AppDatabaseConstructor' has no actual declaration in module`
接著,請定義新的 DAO 介面,或將現有介面移至 commonMain
:
// shared/src/commonMain/kotlin/TodoDao.kt
@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>>
}
將實體定義或移至 commonMain
:
// shared/src/commonMain/kotlin/TodoEntity.kt
@Entity
data class TodoEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String
)
建立平台專屬的資料庫建構工具
您必須定義資料庫建構工具,才能在各個平台上例項化 Room。由於檔案系統 API 的差異,這是唯一需要位於平台專屬來源集中的 API 部分。
Android
在 Android 上,資料庫位置通常是透過 Context.getDatabasePath()
API 取得。如要建立資料庫例項,請指定 Context
和資料庫路徑。
// shared/src/androidMain/kotlin/Database.android.kt
fun getDatabaseBuilder(context: Context): RoomDatabase.Builder<AppDatabase> {
val appContext = context.applicationContext
val dbFile = appContext.getDatabasePath("my_room.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
iOS
如要在 iOS 上建立資料庫執行個體,請使用 NSFileManager
提供資料庫路徑,通常位於 NSDocumentDirectory
中。
// shared/src/iosMain/kotlin/Database.ios.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.desktop.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
建立資料庫例項
從其中一個平台專屬建構函式取得 RoomDatabase.Builder
後,您可以在一般程式碼中設定其餘的 Room 資料庫,並實際例項化資料庫。
// shared/src/commonMain/kotlin/Database.kt
fun getRoomDatabase(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
選取 SQLite 驅動程式
先前的程式碼片段會呼叫 setDriver
建構工具函式,定義 Room 資料庫應使用的 SQLite 驅動程式。這些驅動程式會因目標平台而異。先前的程式碼片段使用 BundledSQLiteDriver
。建議使用這個驅動程式,因為其中包含從來源編譯的 SQLite,可在所有平台提供最一致且最新的 SQLite 版本。
如要使用 OS 提供的 SQLite,請在指定平台專用驅動程式的平台專用來源集中使用 setDriver
API。如要瞭解可用的驅動程式實作方式,請參閱「驅動程式實作方式」。你可以使用下列任一方法:
androidMain
有AndroidSQLiteDriver
位成員iosMain
有NativeSQLiteDriver
位成員
如要使用 NativeSQLiteDriver
,您必須提供連結器選項 -lsqlite3
,讓 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")
}
}
}
設定協同程式情境 (選用)
Android 上的 RoomDatabase
物件可選擇使用 RoomDatabase.Builder.setQueryExecutor()
設定共用應用程式執行器,以執行資料庫作業。
由於執行器與 KMP 不相容,因此 Room 的 setQueryExecutor()
API 無法在 commonMain
中使用。反之,RoomDatabase
物件必須使用 CoroutineContext
進行設定,而 CoroutineContext
可透過 RoomDatabase.Builder.setCoroutineContext()
進行設定。如果未設定任何內容,RoomDatabase
物件預設會使用 Dispatchers.IO
。
壓縮與模糊化
如果專案經過縮減或混淆處理,您必須加入下列 ProGuard 規則,Room 才能找到資料庫定義產生的實作項目:
-keep class * extends androidx.room.RoomDatabase { <init>(); }
遷移至 Kotlin Multiplatform
Room 最初是做為 Android 程式庫開發,之後遷移至 KMP,並著重於 API 相容性。Room 的 KMP 版本在不同平台之間,以及與 Android 專用版本之間,略有差異。這些差異如下所列。
從 Support SQLite 遷移至 SQLite 驅動程式
androidx.sqlite.db
中的 SupportSQLiteDatabase
和其他 API 用法都必須使用 SQLite 驅動程式 API 重構,因為 androidx.sqlite.db
中的 API 僅適用於 Android (請注意,這與 KMP 套件的套件不同)。
為了回溯相容性,只要 RoomDatabase
設定了 SupportSQLiteOpenHelper.Factory
(例如未設定 SQLiteDriver
),Room 就會以「相容模式」運作,支援 SQLite 和 SQLite 驅動程式 API 都能正常運作。這樣就能進行增量遷移,不必一次性將所有支援的 SQLite 用法轉換為 SQLite 驅動程式。
轉換遷移作業子類別
遷移子類別時,需要改用對應的 SQLite 驅動程式:
Kotlin Multiplatform
遷移子類別
object Migration_1_2 : Migration(1, 2) {
override fun migrate(connection: SQLiteConnection) {
// …
}
}
自動遷移規格子類別
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(connection: SQLiteConnection) {
// …
}
}
僅限 Android 裝置
遷移子類別
object Migration_1_2 : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// …
}
}
自動遷移規格子類別
class AutoMigrationSpec_1_2 : AutoMigrationSpec {
override fun onPostMigrate(db: SupportSQLiteDatabase) {
// …
}
}
轉換資料庫回呼
資料庫回呼需要遷移至 SQLite 驅動程式對應項目:
Kotlin Multiplatform
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(connection: SQLiteConnection) {
// …
}
override fun onDestructiveMigration(connection: SQLiteConnection) {
// …
}
override fun onOpen(connection: SQLiteConnection) {
// …
}
}
僅限 Android 裝置
object MyRoomCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
// …
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
// …
}
override fun onOpen(db: SupportSQLiteDatabase) {
// …
}
}
轉換 @RawQuery
DAO 函式
針對非 Android 平台編譯的 @RawQuery
註解函式,需要宣告 RoomRawQuery
類型的參數,而非 SupportSQLiteQuery
。
Kotlin Multiplatform
定義原始查詢
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: RoomRawQuery): List<TodoEntity>
}
接著,您可以使用 RoomRawQuery
在執行階段建立查詢:
suspend fun AppDatabase.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = RoomRawQuery(
sql = "SELECT * FROM TodoEntity WHERE title = ?",
onBindStatement = {
it.bindText(1, title.lowercase())
}
)
return todoDao().getTodos(query)
}
僅限 Android 裝置
定義原始查詢
@Dao
interface TodoDao {
@RawQuery
suspend fun getTodos(query: SupportSQLiteQuery): List<TodoEntity>
}
接著,您可以使用 SimpleSQLiteQuery
在執行階段建立查詢:
suspend fun AndroidOnlyDao.getTodosWithLowercaseTitle(title: String): List<TodoEntity> {
val query = SimpleSQLiteQuery(
query = "SELECT * FROM TodoEntity WHERE title = ?",
bindArgs = arrayOf(title.lowercase())
)
return getTodos(query)
}
轉換封鎖 DAO 函式
Kotlin 為多個平台提供功能豐富的非同步 kotlinx.coroutines
程式庫,Room 因而受益。為獲得最佳功能,系統會對在 KMP 專案中編譯的 DAO 強制執行 suspend
函式,但以 androidMain
實作的 DAO 除外,因為這類 DAO 會與現有程式碼集維持回溯相容性。使用 KMP 適用的 Room 時,為非 Android 平台編譯的所有 DAO 函式都必須是 suspend
函式。
Kotlin Multiplatform
暫停查詢
@Query("SELECT * FROM Todo")
suspend fun getAllTodos(): List<Todo>
暫停交易
@Transaction
suspend fun transaction() { … }
僅限 Android 裝置
封鎖查詢
@Query("SELECT * FROM Todo")
fun getAllTodos(): List<Todo>
封鎖交易
@Transaction
fun blockingTransaction() { … }
將反應式型別轉換為 Flow
並非所有 DAO 函式都需要是暫停函式。傳回反應式型別 (例如 LiveData
或 RxJava 的 Flowable
) 的 DAO 函式不應轉換為暫停函式。不過,部分類型 (例如 LiveData
) 與 KMP 不相容。具有反應式傳回型別的 DAO 函式必須遷移至協同程式流程。
Kotlin Multiplatform
反應類型 Flows
@Query("SELECT * FROM Todo")
fun getTodosFlow(): Flow<List<Todo>>
僅限 Android 裝置
反應式型別,例如 LiveData
或 RxJava 的 Flowable
@Query("SELECT * FROM Todo")
fun getTodosLiveData(): LiveData<List<Todo>>
轉換交易 API
Room KMP 的資料庫交易 API 可以區分寫入 (useWriterConnection
) 和讀取 (useReaderConnection
) 交易。
Kotlin Multiplatform
val database: RoomDatabase = …
database.useWriterConnection { transactor ->
transactor.immediateTransaction {
// perform database operations in transaction
}
}
僅限 Android 裝置
val database: RoomDatabase = …
database.withTransaction {
// perform database operations in transaction
}
寫入交易
使用寫入交易,確保多個查詢會以不可分割的方式寫入資料,讓讀取器能持續存取資料。您可以使用 useWriterConnection
執行這項操作,並搭配下列任一交易類型:
immediateTransaction
:在預寫記錄 (WAL) 模式 (預設) 中,這類交易會在啟動時取得鎖定,但讀取器可以繼續讀取。這是大多數情況下的首選。deferredTransaction
:交易要等到第一個寫入陳述式,才會取得鎖定。如果您不確定交易內是否需要寫入作業,請使用這類交易進行最佳化。舉例來說,如果您只提供播放清單名稱,並開始交易以刪除播放清單中的歌曲,但該播放清單不存在,則不需要任何寫入 (刪除) 作業。exclusiveTransaction
:這個模式的行為與 WAL 模式中的immediateTransaction
完全相同。在其他記錄模式中,這項設定可防止其他資料庫連線在交易進行期間讀取資料庫。
讀取交易
使用讀取交易從資料庫讀取資料多次,確保資料一致。舉例來說,當您有兩個以上的個別查詢,且未使用 JOIN
子句時,讀取器連線只允許延遲交易。嘗試在讀取器連線中啟動即時或專屬交易時,系統會擲回例外狀況,因為這些作業視為「寫入」作業。
val database: RoomDatabase = …
database.useReaderConnection { transactor ->
transactor.deferredTransaction {
// perform database operations in transaction
}
}
不適用於 Kotlin Multiplatform
部分適用於 Android 的 API 不適用於 Kotlin 多平台。
查詢回呼
下列用於設定查詢回呼的 API 不適用於通用平台,因此無法在 Android 以外的平台使用。
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
我們預計在日後的 Room 版本中新增查詢回呼支援。
設定 RoomDatabase
的 API (包含查詢回呼 RoomDatabase.Builder.setQueryCallback
) 和回呼介面 RoomDatabase.QueryCallback
不適用於一般用途,因此無法在 Android 以外的平台使用。
自動關閉資料庫
API RoomDatabase.Builder.setAutoCloseTimeout
可在逾時後自動關閉,但僅適用於 Android,其他平台則無法使用。
預先封裝資料庫
下列 API 可使用現有資料庫 (即預先封裝的資料庫) 建立 RoomDatabase
,但這些 API 不適用於一般用途,因此無法在 Android 以外的平台使用。這些 API 包括:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
我們預計在日後的 Room 版本中,新增對預先封裝資料庫的支援。
多執行個體失效
啟用多例項失效的 API RoomDatabase.Builder.enableMultiInstanceInvalidation
僅適用於 Android,不適用於通用或其他平台。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 將現有應用程式遷移至 Room KMP 程式碼研究室
- 開始使用 KMP 程式碼研究室
- 使用 Room 將資料儲存在本機資料庫