从 Room 2.x 迁移到 Room 3.0

Room 3.0 是一项主要版本更新,可将库转换为 Kotlin 优先。它支持 Kotlin Multiplatform (KMP),需要 Kotlin Symbol Processing (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 Symbol Processing (KSP)。您可以在仍使用 Room 2.x 的情况下进行此过渡。

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

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

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

  2. kaptannotationProcessor 替换为 Room 编译器依赖项的 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 并避免支持 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 块替换为 withWriteTransactionwithReadTransaction
// Before (withTransaction)
db.withTransaction {
    // perform database operations
}

// After (withWriteTransaction - Room 2.8)
import androidx.room.withWriteTransaction

db.withWriteTransaction {
    // perform database operations
}

如果您需要直接对事务连接进行低级访问,也可以将 useWriterConnectionimmediateTransaction 搭配使用。

  • 避免直接使用 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。使用此 API 可在仍使用 Room 2.x 的同时,从旧版 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>
}
  • 分页 (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 观察者移除

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

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

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