Room(Kotlin 多平台)

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 驱动程序。这些驱动程序因目标平台而异。如需了解可用的驱动程序实现,请参阅驱动程序实现

如需了解其他设置信息,请参阅以下内容:

定义数据库类

您需要在共享 KMP 模块的通用源代码集中创建一个带有 @Database 注解的数据库类,以及 DAO 和实体。将这些类放在通用来源中会使它们在所有目标平台之间共享。

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

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

@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 中,数据库位置是通过 NSHomeDirectory 获取。

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

如需创建数据库实例,请提供数据库工厂以及数据库路径。数据库工厂是一个 lambda 函数,它会通过一个类型为 KClass<T> 的接收器调用生成的扩展函数(该函数的名称为 instantiateImpl),其中 T 是带有 @Database 注解的类的类型。

// shared/src/iosMain/kotlin/Database.kt

fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
    val dbFilePath = NSHomeDirectory() + "/my_room.db"
    return Room.databaseBuilder<AppDatabase>(
        name = dbFilePath,
        factory =  { AppDatabase::class.instantiateImpl() }
    )
}

JVM(桌面设备)

如需创建数据库实例,请仅指定数据库路径。您无需提供数据库工厂。

// shared/src/commonMain/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 函数

将 Room 用于 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(Android 专用 DAO 除外)强制执行 suspend 函数,以保持与现有代码库的向后兼容性。

与 KMP 的功能差异

本部分介绍了 Room 的 KMP 和 Android 平台版本之间的功能差异。

@RawQuery DAO 函数

带有 @RawQuery 注解且针对非 Android 平台编译的函数将发生错误。我们打算在未来版本的 Room 中添加对 @RawQuery 的支持。

查询回调

以下用于配置查询回调的 API 不通用,因此在 Android 以外的平台上不可用。

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

我们打算在未来版本的 Room 中添加对查询回调的支持。

用于通过查询回调 RoomDatabase.Builder.setQueryCallback 配置 RoomDatabase 以及回调接口 RoomDatabase.QueryCallback 的 API 不是通用的,因此在 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,在常用平台或其他平台中不可用。