Android框架Anko的SQLite模塊知識點總結
你是否嘗試過使用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學習網
推薦閱讀: