为 KMP 设置 SQLite

androidx.sqlite 库包含抽象接口以及基本实现,可用于构建您自己的访问 SQLite 的库。您可能需要考虑使用 Room 库,该库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。

设置依赖项

如需在 KMP 项目中设置 SQLite,请在模块的 build.gradle.kts 文件中添加制品依赖项:

[versions]
sqlite = "2.7.1"

[libraries]
# The SQLite Driver interfaces
androidx-sqlite = { module = "androidx.sqlite:sqlite", version.ref = "sqlite" }

# The bundled SQLite driver implementation
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }

[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

SQLite 驱动程序 API

androidx.sqlite 库组提供低级 API,用于与 SQLite 库进行通信,这些库要么包含在使用 androidx.sqlite:sqlite-bundled 时的库中,要么包含在使用 androidx.sqlite:sqlite-framework 时的主机平台(例如 Android 或 iOS)中。这些 API 严格遵循 SQLite C API 的核心功能。

主要有三个界面:

以下示例展示了核心 API:

fun main() {
    val databaseConnection = BundledSQLiteDriver().open("todos.db")
    databaseConnection.execSQL(
        "CREATE TABLE IF NOT EXISTS Todo (id INTEGER PRIMARY KEY, content TEXT)"
    )
    databaseConnection.prepare(
        "INSERT OR IGNORE INTO Todo (id, content) VALUES (? ,?)"
    ).use { stmt ->
        stmt.bindInt(index = 1, value = 1)
        stmt.bindText(index = 2, value = "Try Room in the KMP project.")
        stmt.step()
    }
    databaseConnection.prepare("SELECT content FROM Todo").use { stmt ->
        while (stmt.step()) {
            println("Action item: ${stmt.getText(0)}")
        }
    }
    databaseConnection.close()
}

与 SQLite C API 类似,常见的使用模式包括以下步骤:

  1. 使用实例化的 SQLiteDriver 实现打开数据库连接。
  2. 使用 SQLiteConnection.prepare 准备 SQL 语句。
  3. 通过执行以下操作来执行 SQLiteStatement:
    1. 可选:使用 bind* 函数绑定实参。
    2. 使用 step 函数遍历结果集。
    3. 使用 get* 函数从结果集中读取列。

驱动程序实现

下表总结了可用的驱动程序实现:

类名称

制品

支持的平台

AndroidSQLiteDriver androidx.sqlite:sqlite-framework

Android

NativeSQLiteDriver androidx.sqlite:sqlite-framework

iOS、Mac 和 Linux

BundledSQLiteDriver androidx.sqlite:sqlite-bundled

Android、iOS、Mac、Linux 和 JVM(桌面设备)

WebWorkerSQLiteDriver androidx.sqlite:sqlite-web

JavaScript 和 WebAssembly (WasmJS)

建议使用的实现是 androidx.sqlite:sqlite-bundled 中提供的 BundledSQLiteDriver。它包含从源代码编译的 SQLite 库,可在所有受支持的 KMP 平台上提供最新版本和一致性。

SQLite 驱动程序和 Room

驱动程序 API 适用于与 SQLite 数据库进行低级别交互。 对于提供更强大的 SQLite 访问权限的功能丰富的库,我们推荐使用 Room。

RoomDatabase 依赖于 SQLiteDriver 来执行数据库操作,您必须使用 RoomDatabase.Builder.setDriver 配置实现。Room 提供了 RoomDatabase.useReaderConnection 和 RoomDatabase.useWriterConnection,以便更直接地访问托管式数据库连接。

迁移到 Kotlin Multiplatform

您必须将所有低级别支持 SQLite API 组件(例如 SupportSQLiteDatabase 接口)的使用迁移到等效的 SQLite 驱动程序组件。

Kotlin Multiplatform

使用低级 SQLiteConnection 执行事务

val connection: SQLiteConnection = BundledSQLiteDriver().open("todos.db")
connection.execSQL("BEGIN IMMEDIATE TRANSACTION")
try {
    // perform database operations in transaction.
    connection.execSQL("END TRANSACTION")
} catch (t: Throwable) {
    connection.execSQL("ROLLBACK TRANSACTION")
}

执行无结果的查询

val connection: SQLiteConnection = BundledSQLiteDriver().open("todos.db")
connection.execSQL("ALTER TABLE ...")

执行有结果但无参数的查询

val connection: SQLiteConnection = BundledSQLiteDriver().open("todos.db")
connection.prepare("SELECT * FROM Pet").use { statement ->
    while (statement.step()) {
        // read columns.
        statement.getInt(0)
        statement.getText(1)
    }
}

执行包含结果和参数的查询

connection.prepare("SELECT * FROM Pet WHERE id = ?").use { statement ->
    statement.bindInt(1, id)
    if (statement.step()) {
        // row found, read columns.
    } else {
        // row not found.
    }
}

仅限 Android

使用SupportSQLiteDatabase执行交易

val database: SupportSQLiteDatabase = ...
database.beginTransaction()
try {
  // perform database operations in transaction
  database.setTransactionSuccessful()
} finally {
  database.endTransaction()
}

执行无结果的查询

val database: SupportSQLiteDatabase = ...
database.execSQL("ALTER TABLE ...")

执行有结果但无参数的查询

val database: SupportSQLiteDatabase = ...
database.query("SELECT * FROM Pet").use { cursor ->
  while (cursor.moveToNext()) {
    // read columns
    cursor.getInt(0)
    cursor.getString(1)
  }
}

执行包含结果和参数的查询

database.query("SELECT * FROM Pet WHERE id = ?", id).use { cursor ->
  if (cursor.moveToNext()) {
    // row found, read columns
  } else {
    // row not found
  }
}