你是否嘗試過使用Android的遊標(Cursors)來解析SQLite的查詢結果?你必須編寫大量的樣板代碼,僅僅為瞭解析查詢的結果行,並把它包含在數不清的try..finally中,適當的關閉所有打開的資源。

Anko提供了大量的擴展函數,來簡化SQLite資料庫的操作。

內容

  • 在你的工程中使用Anko SQLite
  • 訪問資料庫
  • 創建和刪除表
  • 插入數據
  • 查詢數據
  • 解析查詢結果
  • 自定義行解析器
  • Cursor 流
  • 更新值
  • 事務

在你的工程中使用Anko SQLite

添加 anko-sqlite 依賴至你的 build.gradle文件中:

dependencies {
compile "org.jetbrains.anko:anko-sqlite:$anko\_version"
}

訪問資料庫

如果你使用 SQLiteOpenHelper,你一般會調用 getReadableDatabase() 或是 getWritableDatabase() (在生產代碼中結果其實是一樣的),但是你之後必須調用接收到的SQLiteDatabase對象的 close() 方法。你也需要在某些地方對幫助類進行緩存,並且如果你是在幾個不同的線程中使用它,你必須進行並發處理。以上所用的東西都挺困難的。那也是為什麼Android開發者不熱衷與使用默認的SQLite API而傾向於使用像ORM(對象關係映射)這樣的重量級包裝。

Anko提供了一個特別的類 ManagedSQLiteOpenHelper 用來替換默認的哪一個。 它是這樣子使用的:

class MyDatabaseOpenHelper(ctx: Context) : ManagedSQLiteOpenHelper(ctx, "MyDatabase", null, 1) {
companion object {
private var instance: MyDatabaseOpenHelper? = null

@Synchronized
fun getInstance(ctx: Context): MyDatabaseOpenHelper {
if (instance == null) {
instance = MyDatabaseOpenHelper(ctx.getApplicationContext())
}
return instance!!
}
}

override fun onCreate(db: SQLiteDatabase) {
// Here you create tables
db.createTable("Customer", ifNotExists = true,
"id" to INTEGER + PRIMARY\_KEY + UNIQUE,
"name" to TEXT,
"photo" to BLOB)
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// Here you can upgrade tables, as usual
db.dropTable("User", true)
}
}

// Access property for Context
val Context.database: MyDatabaseOpenHelper
get() = MyDatabaseOpenHelper.getInstance(getApplicationContext())

感覺怎麼樣? 不用在把你的代碼放大 try 語句塊中,現在你只需這麼寫:

database.use {
// `this` is a SQLiteDatabase instance
}

資料庫在執行 {} 中的所有代碼後會被關閉。

非同步調用的例子:

class SomeActivity : Activity() {
private fun loadAsync() {
async(UI) {
val result = bg {
database.use { ... }
}
loadComplete(result)
}
}
}

之前提到的所有方法都可能拋出 SQLiteException。你必須自己去處理它,Anko沒有理由假裝那些錯誤不會發生。

創建和刪除表

使用Anko你可以很方便的創建一個表,或是刪除一個已經存在的表。語法很直接:

database.use {
createTable("Customer", true,
"id" to INTEGER + PRIMARY_KEY + UNIQUE,
"name" to TEXT,
"photo" to BLOB)
}

在SQLite中,有5個主要類型: NULL, INTEGER, REAL, TEXT and BLOB。但是每一行中可能有像 PRIMARY KEY 或是 UNIQUE這樣的修飾符。 你可以使用加號添加到類型名的後面。

使用 dropTable 函數,刪除一個表:

dropTable("User", true)

插入數據

同常情況下, 你需要一個 ContentValues 實例來往表中插入一行數據。這裡有一個例子:

val values = ContentValues()
values.put("id", 5)
values.put("name", "John Smith")
values.put("email", "[email protected]")
db.insert("User", null, values)

Anko讓你直接將想插入的值作為 insert() 函數的參數:

// Where db is an SQLiteDatabase
// eg: val db = database.writeableDatabase
db.insert("User",
"id" to 42,
"name" to "John",
"email" to "[email protected]"
)

或者在 database.use 中使用:

database.use {
insert("User",
"id" to 42,
"name" to "John",
"email" to "[email protected]"
}

注意:在上面的例子中 database 是一個 database helper 的實例,而 db 是一個 SQLiteDatabase 對象。

insertOrThrow(), replace(), replaceOrThrow() 等函數也存在並且有著相同的語義。

查詢數據

Anko提供了一個很方便的查詢建造器。它可以通過 db.select(tableName, vararg columns) 創建,其中 db是 SQLiteDatabase的一個實例。

方法描述column(String)Add a column to select querydistinct(Boolean)Distinct querywhereArgs(String)Specify raw String where querywhereArgs(String, args)Specify a where query with argumentswhereSimple(String, args)Specify a where query with ? mark argumentsorderBy(String, [ASC/DESC])Order by this columngroupBy(String)Group by this columnlimit(count: Int)Limit query result row countlimit(offset: Int, count: Int)Limit query result row count with an offsethaving(String)Specify raw having expressionhaving(String, args)Specify a having expression with arguments

加粗的函數使用一種特殊的方法來解析參數. 他們允許你以任意的順序對參數進行賦值:

db.select("User", "name")
.whereArgs("(_id > {userId}) and (name = {userName})",
"userName" to "John",
"userId" to 42)

在這裡, {userId} 將會被 42 替換, {userName} 被 John替換。 如果類型不是數字類型 (Int, Float等等。) 或 Boolean 型,值可能會被轉義。 對於其他對一些類型,可能會使用到 toString()。

whereSimple 接受 String 類型到參數。 它工作起來和 SQLiteDatabase 中的 query())一樣 (問號標記 ? 會被參數中的真實值所替代)。

我們怎樣執行查詢操作?使用 exec() 函數。她接受一個 Cursor.() -> T類型的擴展函數。 它接收擴展函數然後它來關閉 Cursor ,你不要自己去做這件事:

db.select("User", "email").exec {
// Doing some stuff with emails
}

解析查詢結果

我們擁有一些 Cursor,我們怎麼將它解析成一個標準的類呢? Anko提供了 parseSingle, parseOpt 和 parseList 函數,使得這件事做起來更加簡單。

方法描述parseSingle(rowParser): TParse exactly one rowparseOpt(rowParser): T?Parse zero or one rowparseList(rowParser): List<T>Parse zero or more rows

注意: 如果接收到的 Cursor包含超過一行的數據, parseSingle() 和 parseOpt() 將會拋出異常。

現在的問題是: 什麼是 rowParser?每個函數都支持兩種不同類型的解析器: RowParser 和 MapRowParser:

interface RowParser<T> {
fun parseRow(columns: Array<Any>): T
}

interface MapRowParser<T> {
fun parseRow(columns: Map<String, Any>): T
}

如果你想讓你的查詢效率更高,RowParser (當你必須知道每一列的序號)。 parseRow 接受一個 Any 列表( Any 可以是除了 Long, Double, String 或是 ByteArray 之外的任何類型)。 MapRowParser,你可以使用欄位的名稱來獲取它的值。

Anko 已經提供了以下這些單列單行解析器:

  • ShortParser
  • IntParser
  • LongParser
  • FloatParser
  • DoubleParser
  • StringParser
  • BlobParser

你也可以在類的構造函數中創建一個行解析器。假設你有一個類:

class Person(val firstName: String, val lastName: String, val age: Int)

解析就像下面這麼簡單:

val rowParser = classParser<Person>()

目前為止,對於主構造函數中存在可選參數的類不支持創建解析器。注意,構造函數是通過反射進行調用的,對於很大的數據集,還是使用 RowParser 比較好。

如果你使用了 db.select() 建造器, 你可以直接在裡面調用 parseSingle, parseOpt 和 parseList 並傳入一個適當的解析器。

自定義行解析器

直接上實例,為 (Int, String, String) 這些列創建一個解析器。 最幼稚的做法是這樣子:

class MyRowParser : RowParser<Triple<Int, String, String>> {
override fun parseRow(columns: Array<Any>): Triple<Int, String, String> {
return Triple(columns[0] as Int, columns[1] as String, columns[2] as String)
}
}

很好,現在在你的代碼中有三個顯式的轉換。讓我們使用rowParser函數來去除他們:

val parser = rowParser { id: Int, name: String, email: String ->
Triple(id, name, email)
}

就是這樣子! rowParser 將所有轉換都隱藏了起來,你可以按照自己的心情命名lambda表達式中的參數。

Cursor 流

Anko提供了一種函數式的方式訪問 SQLite的 Cursor 。只要調用 cursor.asSequence() 或 cursor.asMapSequence() 擴展函數來獲取數據行的序列。 不要忘記關閉 Cursor :)

更新值

為其中的一個user賦予一個新的名字:

update("User", "name" to "Alice")
.where("_id = {userId}", "userId" to 42)
.exec()

Update 也有一個 whereSimple() 方法,如果你是一個傳統的人就使用它:

update("User", "name" to "Alice")
.`whereSimple`("_id = ?", 42)
.exec()

事務

有一個叫做 transaction() 的函數,允許你將幾個資料庫操作放到一個SQLite的事務中。

transaction {
// Your transaction code
}

當 {} 塊中沒有拋出任何異常時,事務才會標記為成功。

英文原文地址

更多Kotlin的內容歡迎關注Kotlin學習網

推薦閱讀:

相關文章