从 Room 2.x 迁移到 Room 3.0

Room 3.0 是一项主要版本更新,可将库转换为 Kotlin 优先。它支持 Kotlin Multiplatform (KMP),需要 Kotlin 符号处理 (KSP),并强制使用协程进行异步操作。

为防止与现有 Room 2.x 应用和传递依赖项出现兼容性问题,Room 3.0 位于新软件包 androidx.room3 中。

本指南概述了将现有 Room 2.x 实现迁移到 Room 3.0 所需的步骤。

Room 3.0 中的主要变化

在开始迁移之前,请先熟悉以下主要差异:

  • 新软件包和工件:所有类都位于 androidx.room3 中。 工件使用 room3 前缀,例如 androidx.room3:room3-runtime
  • 仅限 Kotlin 和 KSP:Room 3.0 不支持 Java 代码生成。请使用 KSP 而不是 KAPT 或 Java 注解处理器。Room 3.0 仍支持将 Java 源代码作为输入。
  • 协程优先:DAO 函数必须是 suspend 函数,但 可观测类型除外。CoroutineContext 会替换执行器。
  • 不支持 SupportSQLiteSQLiteDriver API 支持 Room。Room 从核心 API 中移除了 SupportSQLiteDatabase
  • API 更改:迁移和数据库回调使用 SQLiteConnection 而不是 SupportSQLiteDatabase
  • 响应式类型的转换器:RxJava、LiveData、Guava 和 Paging 返回类型要求您注册 @DaoReturnTypeConverters

我们建议分两个不同的阶段进行迁移:首先在 Room 2.x 中准备和现代化您的代码库,然后切换到 Room 3.0。


在 Room 2.x 中准备和现代化

在迁移到 Room 3.0 之前,您可以通过更新到当前的 Room 2.x 版本(例如 Room 2.8)来执行大部分现代化工作。Room 2.8 支持 Kotlin Multiplatform (KMP),并包含 Room 3.0 使用的许多驱动程序 API。

更新到 Room 2.8 及更高版本

更新 build 配置以使用当前的 Room 2.x 版本:

[versions]
room2 = "2.8.4" # Use the current Room 2.8 version

[libraries]
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room2" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room2" }

从 KAPT 迁移到 KSP

Room 3.0 不支持 Java 注解处理器或 KAPT。您必须使用 Kotlin 符号处理 (KSP)。您可以在仍使用 Room 2.x 的同时进行此转换。

  1. 在模块的 build.gradle.kts 中,应用 KSP 插件:

    plugins {
        id("com.google.devtools.ksp") version "<ksp_version>"
    }
    

    确保 KSP 版本与您的 Kotlin 版本兼容。

  2. 对于 Room 编译器依赖项,将 kaptannotationProcessor 替换为 ksp

    dependencies {
        implementation(libs.androidx.room.runtime)
        ksp(libs.androidx.room.compiler)
    }
    

采用协程

Room 3.0 要求使用协程进行异步操作。

  • 更新您的 DAO:除非它们返回可观测的响应式类型(例如 Flow 或 RxJava 类型),否则所有 DAO 函数都必须是 suspend 函数。
// Before (Blocking)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAll(): List<User>
}

// After (Suspend)
@Dao
interface UserDao {
    @Query("SELECT * FROM User")
    suspend fun getAll(): List<User>
}
  • 如果您使用自定义 Executor 配置了 RoomDatabase 以执行数据库操作,请使用构建器上的 setQueryCoroutineContext 迁移到 CoroutineContext
Room.databaseBuilder<AppDatabase>(context, "db")
    .setQueryCoroutineContext(Dispatchers.IO)
    .build()

采用驱动程序 API 并避免使用 Support SQLite

Room 3.0 完全由 SQLiteDriver 提供支持,并且不再在其核心 API 中支持 SupportSQLiteDatabase

如果您未调用 setDriver 在数据库构建器上设置 SQLiteDriver,则 Room 2.8 会在兼容性模式下运行,其中 Support SQLite 和驱动程序 API 均可正常运行。借助此兼容性模式,您可以在启用驱动程序之前逐步转换代码库。

  • 转换迁移:迁移 MigrationAutoMigrationSpec 子类以使用 SQLiteConnection 而不是 SupportSQLiteDatabase
// Before (SupportSQLiteDatabase)
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
    }
}

// After (SQLiteConnection - Room 2.8)
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.execSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(connection: SQLiteConnection) {
        connection.execSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • 转换数据库回调:更新 RoomDatabase.Callback 实现以使用 SQLiteConnection
// Before (SupportSQLiteDatabase)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(db: SupportSQLiteDatabase) {
        // ...
    }
}

// After (SQLiteConnection - Room 2.8)
val callback = object : RoomDatabase.Callback() {
    override fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}
  • 转换 @RawQuery DAO 函数:对于使用 @RawQuery 注解的函数,请使用 RoomRawQuery 而不是 SupportSQLiteQuery
// Before (SupportSQLiteQuery)
@Dao
interface UserDao {
    @RawQuery
    fun getUser(query: SupportSQLiteQuery): User
}

// After (RoomRawQuery)
@Dao
interface UserDao {
    @RawQuery
    suspend fun getUser(query: RoomRawQuery): User
}

您可以在运行时构建 RoomRawQuery

val query = RoomRawQuery(
    sql = "SELECT * FROM User WHERE id = ?",
    onBindStatement = { statement ->
        statement.bindInt(1, userId)
    }
)
  • 转换事务 API:将仅限 Android 的 withTransactionrunInTransaction 块替换为 useWriterConnectionimmediateTransaction
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (useWriterConnection - Room 2.8)
import androidx.room.useWriterConnection
import androidx.room.immediateTransaction

db.useWriterConnection { connection ->
    connection.immediateTransaction {
        // perform database operations
    }
}
  • 避免直接使用 SupportSQLiteDatabase:如果您有大量仍需要 SupportSQLiteDatabase 的旧版代码,但您还无法迁移 这些代码,请使用 androidx.room:room-sqlite-wrapper 兼容性工件:
dependencies {
    implementation("androidx.room:room-sqlite-wrapper:$roomVersion")
}

然后,使用 getSupportWrapper 从 Room 数据库实例获取 SupportSQLiteDatabase

import androidx.room.support.getSupportWrapper

val legacyDb: SupportSQLiteDatabase = roomDatabase.getSupportWrapper()
  • 设置 SQLite 驱动程序:将所有 Room API 用法迁移到驱动程序 API 后,通过在您的 RoomDatabase 构建器中调用 setDriver 来配置驱动程序,例如 BundledSQLiteDriverAndroidSQLiteDriver
import androidx.sqlite.driver.bundled.BundledSQLiteDriver

val db = Room.databaseBuilder<AppDatabase>(context, "db")
    .setDriver(BundledSQLiteDriver())
    .build()

采用基于流的失效跟踪

Room 2.8 引入了 InvalidationTracker.createFlow API。在仍使用 Room 2.x 的同时,使用此 API 迁移离开旧版 InvalidationTracker.Observer 实现。这样可以为 Room 3.0 准备代码库,后者会完全移除 Observer

// Before (InvalidationTracker.Observer)
val observer = object : InvalidationTracker.Observer("User") {
    override fun onInvalidated(tables: Set<String>) {
        // reload user data
    }
}
db.invalidationTracker.addObserver(observer)

// After (createFlow - Room 2.8)
val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}

迁移到 Room 3.0

在 Room 2.x 上现代化应用后,转换到 Room 3.0 涉及更新依赖项、软件包导入和数据库回调。

更新依赖项和软件包导入

  • 在 build 配置中,将 androidx.room 依赖项替换为 androidx.room3
[versions]
room3 = "3.0.0" # Use the current Room 3.0 version

[libraries]
androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room3" }
androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room3" }
  • 更新依赖项块:
dependencies {
    implementation(libs.androidx.room3.runtime)
    ksp(libs.androidx.room3.compiler)
}
  • 更新软件包导入。将 import androidx.room.* 替换为 import androidx.room3.*

更新类型转换器 API

Room 3.0 会重命名类型转换器 API,以明确其用于转换列值的用法,并避免与 DAO 返回类型转换器混淆。

更新代码库中的以下注解和函数:

  • @TypeConverter 重命名为 @ColumnTypeConverter
  • @TypeConverters 重命名为 @ColumnTypeConverters
  • @ProvidedTypeConverter 重命名为 @ProvidedColumnTypeConverter
  • RoomDatabase.Builder.addTypeConverter 重命名为 addColumnTypeConverter

示例:

// Before
@ProvidedTypeConverter
class Converters {
  @TypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addTypeConverter(convertersInstance)
  .build()
// After
import androidx.room3.ColumnTypeConverter
import androidx.room3.ColumnTypeConverters
import androidx.room3.ProvidedColumnTypeConverter

@ProvidedColumnTypeConverter
class Converters {
  @ColumnTypeConverter
  fun fromTimestamp(value: Long?): Date? = ...
}

@Database(entities = [User::class], version = 1)
@ColumnTypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()

val db = Room.databaseBuilder<AppDatabase>(...)
  .addColumnTypeConverter(convertersInstance)
  .build()

将回调更新为挂起函数

在 Room 3.0 中,数据库回调和迁移使用 SQLiteConnection,并且是 suspend 函数。

  • 更新手动 Migration 类:
import androidx.sqlite.SQLiteConnection
import androidx.sqlite.async.executeSQL

val MIGRATION_1_2 = object : Migration(1, 2) {
    override suspend fun migrate(connection: SQLiteConnection) {
        connection.executeSQL(
            "ALTER TABLE User ADD COLUMN age INTEGER DEFAULT 0 NOT NULL"
        )
    }
}
  • 更新 RoomDatabase.Callback 实现:
val callback = object : RoomDatabase.Callback() {
    override suspend fun onCreate(connection: SQLiteConnection) {
        // ...
    }
}

注册 DAO 返回值类型转换器

在 Room 3.0 中,响应式返回值类型(例如 RxJava、LiveData、Guava 和 Paging)要求您使用 @DaoReturnTypeConverters 注册 DAO 返回值类型转换器。

import androidx.room3.paging.PagingSourceDaoReturnTypeConverter

@Dao
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
interface UserDao {
    @Query("SELECT * FROM User")
    fun getAllPaginated(): PagingSource<Int, User>
}
  • Paging (PagingSource):从 androidx.room3:room3-paging 工件注册 PagingSourceDaoReturnTypeConverter
  • RxJava (ObservableFlowableSingleMaybeCompletable) :从 androidx.room3:room3-rxjava3 工件注册 RxDaoReturnTypeConverters
  • Guava (ListenableFuture) :从 androidx.room3:room3-guava 工件注册 GuavaDaoReturnTypeConverter
  • LiveData (LiveData):从 androidx.room3:room3-livedata工件注册LiveDataDaoReturnTypeConverter

验证 InvalidationTracker Observer 移除情况

Room 3.0 完全移除了 InvalidationTracker.Observer 和相关注册方法,例如 addObserverremoveObserver

如果您尚未在 第 1 阶段转换为协程流,则必须将所有 Observer 用法迁移到 createFlow

val userFlow = db.invalidationTracker.createFlow("User").map { _ ->
    userDao.getAllUsers()
}