Room(Kotlin 多平台)

Room 持久性库在 SQLite 上提供了一个抽象层,以允许 ,在充分利用 SQLite 的强大功能的同时,实现更稳健的数据库访问。这个 页面重点介绍了如何在 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 注解的数据库类以及 DAO 共享 KMP 模块的公共源代码集中内的实体和实体。放置 这些常用来源中的类将允许在所有目标之间共享 平台。

// shared/src/commonMain/kotlin/Database.kt

@Database(entities = [TodoEntity::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() {
  abstract fun getDao(): TodoDao
}

expect object AppDatabaseConstructor : RoomDatabaseConstructor<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 中唯一需要包含在平台专用源代码中的部分 因为文件系统 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 库开发的,后来迁移到了 侧重于 API 兼容性的 KMP。KMP 版本的 Room 有所不同 以及从 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 受益于功能丰富的异步 kotlinx.coroutines 库 Kotlin 为多个平台提供的各种功能为实现最佳功能,suspend 函数会强制执行在 KMP 项目中编译的 DAO,但 Android 专用 DAO,旨在保持与现有 DAO 的向后兼容性 代码库。

与 KMP 的功能差异

本部分介绍了 KMP 和 Android 平台在功能上有何不同 版本。

@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 设备, 在其他平台不可用。

预包装数据库

以下 API 使用现有数据库(即RoomDatabase 预封装数据库)是不可共用的,因此无法在 Android 之外的其他平台这些 API 是:

  • RoomDatabase.Builder.createFromAsset
  • RoomDatabase.Builder.createFromFile
  • RoomDatabase.Builder.createFromInputStream
  • RoomDatabase.PrepackagedDatabaseCallback

我们打算在 Room。

多实例失效

用于启用多实例失效的 API RoomDatabase.Builder.enableMultiInstanceInvalidation仅适用于 Android 平台,不适用于通用平台或其他平台。