Room (Kotlin Multiplatform)

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 驅動程式。這些驅動程式會因目標平台而異。如需可用的驅動程式實作說明,請參閱驅動程式實作

如需其他設定資訊,請參閱下列資源:

定義資料庫類別

您需要在共用 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

如要建立資料庫執行個體,請指定 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

如要建立資料庫執行個體,請提供資料庫工廠以及資料庫路徑。資料庫工廠是 lambda 函式,會叫用系統產生的擴充功能函式,該函式名稱為 instantiateImpl,接收器類型為 KClass<T>,其中 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 版本。如果您想使用 OS 提供的 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 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() { // … }
}

借助 Kotlin 為多個平台提供的功能豐富的非同步 kotlinx.coroutines 程式庫,Room 可帶來許多好處。為達到最佳功能,系統會針對在 KMP 專案中編譯的 DAO 強制執行 suspend 函式,但 Android 專屬 DAO 除外,以便維持與現有程式碼集的回溯相容性。

KMP 的功能差異

本節說明 KMP 和 Android 平台版本的 Room 功能差異。

@RawQuery DAO 函式

加註 @RawQuery 的函式若針對非 Android 平台進行編譯,就會產生錯誤。我們會在日後的 Room 版本中新增對 @RawQuery 的支援。

查詢回呼

下列用來設定查詢回呼的 API 並不常見,因此不適用於 Android 以外的平台。

  • RoomDatabase.Builder.setQueryCallback
  • RoomDatabase.QueryCallback

我們會在日後的 Room 版本中新增查詢回呼的支援。

使用查詢回呼 RoomDatabase.Builder.setQueryCallback 以及回呼介面 RoomDatabase.QueryCallback 的 API 並不常見,因此不適用於 Android 以外的平台。RoomDatabase

自動關閉資料庫

用來在逾時後啟用自動關閉的 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,不可用於通用平台或其他平台。