從 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 會取代執行器。
  • 不支援 SupportSQLiteSQLiteDriverAPI 會返回 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 以上版本

更新建構設定,使用目前的 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 替換為 ksp,以取得 Room 編譯器依附元件:

    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 會以相容模式運作,支援 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 從舊版 InvalidationTracker.Observer 實作項目遷移,同時仍使用 Room 2.x。這項操作會為 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,請更新依附元件、套件匯入項目和資料庫回呼。

更新依附元件和套件匯入項目

  • 在建構設定中,將 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,明確說明這些 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 中,資料庫回呼和遷移作業會使用 SQLiteConnectionsuspend 函式。

  • 更新手動 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()
}