你是否尝试过使用Android的游标(Cursors)来解析SQLite的查询结果?你必须编写大量的样板代码,仅仅为了解析查询的结果行,并把它包含在数不清的try..finally
中,适当的关闭所有打开的资源。
Anko提供了大量的扩展函数,来简化SQLite数据库的操作。
内容
在你的工程中使用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", "user@domain.org") 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 "user@domain.org" )
或者在 database.use
中使用:
database.use { insert("User", "id" to 42, "name" to "John", "email" to "user@domain.org" }
注意:在上面的例子中 database
是一个 database helper 的实例,而 db
是一个 SQLiteDatabase
对象。
insertOrThrow()
, replace()
, replaceOrThrow()
等函数也存在并且有着相同的语义。
查询数据
Anko提供了一个很方便的查询建造器。它可以通过 db.select(tableName, vararg columns)
创建,其中 db
是 SQLiteDatabase
的一个实例。
方法 | 描述 |
---|---|
column(String) | Add a column to select query |
distinct(Boolean) | Distinct query |
whereArgs(String) | Specify raw String where query |
whereArgs(String, args) | Specify a where query with arguments |
whereSimple(String, args) | Specify a where query with ? mark arguments |
orderBy(String, [ASC/DESC]) | Order by this column |
groupBy(String) | Group by this column |
limit(count: Int) | Limit query result row count |
limit(offset: Int, count: Int) | Limit query result row count with an offset |
having(String) | Specify raw having expression |
having(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
中的 <a target="_blank title=" null"="" style="word-wrap: break-word; color: rgb(59, 67, 72); word-break: break-all;">query()
)一样 (问号标记 ?
会被参数中的真实值所替代)。
我们怎样执行查询操作?使用 exec()
函数。她接受一个 Cursor.() -> T
类型的扩展函数。 它接收扩展函数然后它来关闭 Cursor
,你不要自己去做这件事:
db.select("User", "email").exec { // Doing some stuff with emails }
解析查询结果
我们拥有一些 Cursor
,我们怎么将它解析成一个标准的类呢? Anko提供了 parseSingle
, parseOpt
和 parseList
函数,使得这件事做起来更加简单。
方法 | 描述 |
---|---|
parseSingle(rowParser): T | Parse exactly one row |
parseOpt(rowParser): T? | Parse zero or one row |
parseList(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 }
当 {}
块中没有抛出任何异常时,事务才会标记为成功。