Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。本页重点介绍如何在 Kotlin Multiplatform (KMP) 项目中使用 Room。如需详细了解如何使用 Room,请参阅使用 Room 将数据保存到本地数据库或我们的官方示例。
设置依赖项
如需在 KMP 项目中设置 Room,请在 KMP 模块的 build.gradle.kts 文件中添加相应工件的依赖项。
在 libs.versions.toml 文件中定义依赖项:
[versions]
room = "2.8.4"
sqlite = "2.6.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 与 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 版本。
如果您想使用操作系统提供的 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 中不可用。相反,必须使用 CoroutineContext 配置 RoomDatabase 对象,而 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 特定的版本之间存在一些差异。这些差异如下所列,并附有说明。
从支持 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 驱动程序。
使用 Room SQLite 封装容器(可选)
androidx.room:room-sqlite-wrapper 制品提供了在迁移期间连接 SQLiteDriver 和 SupportSQLiteDatabase 的 API。
如需从配置了 SQLiteDriver 的 RoomDatabase 获取 SupportSQLiteDatabase,请使用新的扩展函数 RoomDatabase.getSupportWrapper()。此兼容性封装容器有助于在采用 SQLiteDriver 的同时,保持 SupportSQLiteDatabase(通常从 RoomDatabase.openHelper.writableDatabase 获取)的现有用法,尤其适用于具有广泛 SupportSQLite API 用法的代码库,这些代码库希望使用 BundledSQLiteDriver。
转换迁移子类
迁移子类需要迁移到 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 函数
如果使用 @RawQuery 注释的函数是为非 Android 平台编译的,则需要声明类型为 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 函数
Room 受益于 Kotlin 为多个平台提供的功能丰富的异步 kotlinx.coroutines 库。为了获得最佳功能,对于在 KMP 项目中编译的 DAO,系统会强制执行 suspend 函数,但以 androidMain 实现的 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 函数必须迁移到协程 flow。
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 Multiplatform 中不可用。
查询回调
以下用于配置查询回调的 API 在通用版中不可用,因此在 Android 以外的平台中也不可用。
RoomDatabase.Builder.setQueryCallbackRoomDatabase.QueryCallback
我们打算在未来的 Room 版本中添加对查询回调的支持。
用于配置 RoomDatabase 的 API(带有查询回调 RoomDatabase.Builder.setQueryCallback)以及回调接口 RoomDatabase.QueryCallback 在 common 中不可用,因此在 Android 以外的其他平台中也不可用。
自动关闭数据库
用于在超时后启用自动关闭功能的 API RoomDatabase.Builder.setAutoCloseTimeout 仅在 Android 上可用,在其他平台上不可用。
预打包数据库
以下 API 用于使用现有数据库(即预打包数据库)创建 RoomDatabase,但这些 API 在通用平台中不可用,因此在 Android 以外的其他平台中也不可用。这些 API 包括:
RoomDatabase.Builder.createFromAssetRoomDatabase.Builder.createFromFileRoomDatabase.Builder.createFromInputStreamRoomDatabase.PrepackagedDatabaseCallback
我们打算在未来版本的 Room 中添加对预打包数据库的支持。
多实例失效
用于启用多实例失效的 API RoomDatabase.Builder.enableMultiInstanceInvalidation 仅在 Android 上可用,在通用平台或其他平台上不可用。
为您推荐
- 注意:当 JavaScript 处于关闭状态时,系统会显示链接文字
- “将现有应用迁移到 Room KMP”Codelab
- “KMP 使用入门”Codelab
- 使用 Room 将数据保存到本地数据库