SQLite (platforma Kotlin wieloplatformowa)

Biblioteka androidx.sqlite zawiera abstrakcyjne interfejsy wraz z podstawowymi implementacjami, których można używać do tworzenia własnych bibliotek, które mają dostęp do SQLite. Warto rozważyć użycie biblioteki Room, która zapewnia warstwę abstrakcji nad SQLite, aby umożliwić bardziej niezawodny dostęp do bazy danych przy jednoczesnym wykorzystaniu pełnej mocy SQLite.

Konfigurowanie zależności

Aby skonfigurować SQLite w projekcie KMP, dodaj zależności artefaktów w pliku build.gradle.kts modułu:

[versions]
sqlite = "2.5.2"

[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" }

Interfejsy API sterownika SQLite

Biblioteki androidx.sqlite oferują interfejsy API niskiego poziomu do komunikacji z biblioteką SQLite, która jest dołączana do biblioteki podczas korzystania z androidx.sqlite:sqlite-bundled lub na platformie hosta, np. Androida lub iOS, podczas korzystania z androidx.sqlite:sqlite-framework. Interfejsy API są ściśle zgodne z podstawowymi funkcjami interfejsu SQLite C API.

Dostępne są 3 główne interfejsy:

  • SQLiteDriver – to punkt wejścia do korzystania z bazy danych SQLite. Odpowiada za otwieranie połączeń z bazą danych.
  • SQLiteConnection – reprezentacja obiektu sqlite3.
  • SQLiteStatement – reprezentacja obiektu sqlite3_stmt.

Poniższy przykład przedstawia podstawowe interfejsy 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()
}

Podobnie jak w przypadku interfejsów API SQLite C, typowe użycie polega na:

  • Otwórz połączenie z bazą danych za pomocą utworzonego wystąpienia SQLiteDriver implementacji.
  • Przygotuj instrukcję SQL za pomocą SQLiteConnection.prepare()
  • Wykonaj SQLiteStatement w ten sposób:
    1. Opcjonalnie możesz powiązać argumenty za pomocą funkcji bind*().
    2. Iteruj po zbiorze wyników za pomocą funkcji step().
    3. Odczytuj kolumny z zestawu wyników za pomocą funkcji get*().

Implementacje sterowników

W tabeli poniżej znajdziesz podsumowanie dostępnych implementacji sterowników:

Nazwa zajęć

Artefakt

Obsługiwane platformy

AndroidSQLiteDriver androidx.sqlite:sqlite-framework

Android

NativeSQLiteDriver androidx.sqlite:sqlite-framework

iOS, Mac i Linux

BundledSQLiteDriver androidx.sqlite:sqlite-bundled

Android, iOS, Mac, Linux i JVM (Desktop)

Zalecana implementacja to BundledSQLiteDriver dostępna w androidx.sqlite:sqlite-bundled. Zawiera bibliotekę SQLite skompilowaną z kodu źródłowego, która zapewnia najbardziej aktualną wersję i spójność na wszystkich obsługiwanych platformach KMP.

Sterownik SQLite i Room

Interfejsy API sterownika są przydatne w przypadku interakcji niskiego poziomu z bazą danych SQLite. Jeśli potrzebujesz rozbudowanej biblioteki, która zapewnia bardziej niezawodny dostęp do SQLite, zalecamy użycie Room.

RoomDatabase korzysta z SQLiteDriver do wykonywania operacji na bazie danych, a implementację należy skonfigurować za pomocą RoomDatabase.Builder.setDriver(). Room udostępnia RoomDatabase.useReaderConnectionRoomDatabase.useWriterConnection, aby zapewnić bardziej bezpośredni dostęp do zarządzanych połączeń z bazą danych.

Migracja do Kotlin Multiplatform

Wszystkie wywołania SQLite niskiego poziomu muszą zostać przeniesione do odpowiedników w sterowniku SQLite.

Kotlin Multiplatform

Przeprowadzanie transakcji za pomocą interfejsu SQLiteConnection niskiego poziomu

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

Wykonaj zapytanie bez wyniku

val connection: SQLiteConnection = ...
connection.execSQL("ALTER TABLE ...")

Wykonaj zapytanie z wynikiem, ale bez argumentów

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

Wykonaj zapytanie z wynikiem i argumentami

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

Tylko Android

Przeprowadź transakcję za pomocą SupportSQLiteDatabase

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

Wykonaj zapytanie bez wyniku

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

Wykonaj zapytanie z wynikiem, ale bez argumentów

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

Wykonaj zapytanie z wynikiem i argumentami

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