Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 强大功能的同时,实现更强大的数据库访问。此页面重点介绍在 Kotlin 多平台 (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
)
请注意,您可以选择使用 actual/expect 声明 来创建特定于平台的 Room 实现。例如,您可以使用 expect
在公共代码中添加特定于平台的 DAO,然后使用特定于平台的源集中的附加查询指定 actual
定义。
创建数据库构建器
您需要定义一个数据库构建器,以便在每个平台上实例化 Room。这是唯一需要在特定于平台的源集中实现的 API 部分,因为文件系统 API 存在差异。例如,在 Android 中,数据库位置通常是通过 Context.getDatabasePath()
API 获取的,而在 iOS 中,数据库位置是使用 NSFileManager
获取的。
Android
要创建数据库实例,请指定 Context 和数据库路径。
// 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,
)
}
数据库实例化
从平台特定的构造函数之一获取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 函数
在 KMP 中使用 Room 时,为非 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 在通用代码中不可用,因此在 Android 以外的平台上也不可用。
RoomDatabase.Builder.setQueryCallback
RoomDatabase.QueryCallback
我们打算在 Room 的未来版本中添加对查询回调的支持。
使用查询回调配置RoomDatabase
的 API RoomDatabase.Builder.setQueryCallback
以及回调接口RoomDatabase.QueryCallback
在通用代码中不可用,因此在 Android 以外的其他平台上也不可用。
自动关闭数据库
用于在超时后启用自动关闭的 API RoomDatabase.Builder.setAutoCloseTimeout
仅在 Android 上可用,在其他平台上不可用。
预打包数据库
以下使用现有数据库(即预打包数据库)创建RoomDatabase
的 API 在通用代码中不可用,因此在 Android 以外的其他平台上也不可用。这些 API 是:
RoomDatabase.Builder.createFromAsset
RoomDatabase.Builder.createFromFile
RoomDatabase.Builder.createFromInputStream
RoomDatabase.PrepackagedDatabaseCallback
我们打算在 Room 的未来版本中添加对预打包数据库的支持。
多实例失效
启用多实例失效的 API RoomDatabase.Builder.enableMultiInstanceInvalidation
仅在 Android 上可用,在通用代码或其他平台上不可用。