Skip to content

Latest commit

 

History

History
404 lines (327 loc) · 12.9 KB

Anko-SQLite.md

File metadata and controls

404 lines (327 loc) · 12.9 KB

你是否已经厌倦了用 Android cursors 解析 SQLite 查询的结果,每次必须编写大量的模板代码才能解析出查询结果,并将其封装在无数的 try..finally 块中以保证能够释放所有的资源。

Anko 提供了大量的扩展功能,大大简化了 SQLite 的使用方式。

概要

在你的项目中添加依赖

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

访问数据库

如果使用 SQLiteOpenHelper, 一般是调用 getReadableDatabase() 或者 getWritableDatabase() , 但是必须记住在使用后调用 close 来释放资源 ,并且需要把 SQLiteOpenHelper 对象缓存起来,如果还想在多个线程中使用同一个 helper 对象,还要解决并发访问的问题 。 这一切都很难。这就是为什么开发者都不太喜欢默认的 SQLite API,而是更喜欢一些第三方的 ORM 库。

Anko 提供了一个特别的类 ManagedSQLiteOpenHelper ,可以无缝地替换掉默认的 API。下面就看看如何使用它:

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) {
        //创建表
        db.createTable("User",true,
                "id" to INTEGER + PRIMARY_KEY + UNIQUE,  //(1)
                "name" to TEXT,
                "sex" to TEXT)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        //数据库版本变更后回调
    }
}

// 在 Context 下提供一个访问的变量
val Context.database: MyDatabaseOpenHelper
    get() = MyDatabaseOpenHelper.getInstance(getApplicationContext())

(1)我运行的时候这里会报错(报错的原因是第三个关键字无法正确的解析,不知道是不是 bug 0.0),这里如果创建不成功先改成两个关键字就行了("id" to INTEGER + PRIMARY_KEY)。

在 use 中 的语句块自动被套上 try 块,并且语句块执行完毕后自动调用 close 方法释放资源,在 use 语句块中包含了 SQLiteDatabase 的实例,也就是可以直接在语句块中使用 SQLiteDatabase 实例的所有 public 属性和方法。

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

异步调用示例:

async(UI) {
    //在主线程中
    val result = bg {
        //在子线程中执行对数据库的操作
        database.use {
            //可以直接使用 SQLiteDatabase 对象的所有方法
            //insert()
            //query()
            //......
        }
    }
    //在主线程中对结果进行处理
    //loadComplete(result)
}
注意
  • use 语句块中的代码可能会抛出 SQLiteException , 如果你认为代码可能出错,需要自己手动捕获处理异常,不然程序会崩溃,如果你确定代码不会有任何问题,可以不做任何处理。
  • bg 语句块会忽略其中的异常(至少我在本篇例子中操作数据库时是这样),所以如果你按照上面的 『异步调用示例』写代码,当 SQLiteException 异常发生时,不会有任何提示,这里要小心了,所以用 bg 语句块的时候最好手动捕获异常:
      val result = bg {
         try {
            database.use {
               ...
            }
         } catch (e: SQLiteException){
             e.printStackTrace()
         }
      }

表的创建和删除

使用 Anko,您可以轻松的创建新并删除现有的表。语法很简单。

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

在 SQLite 中, 有 5 种主要类型: NULL, INTEGER, REAL, TEXTBLOB. 但每列可能有一些修饰符,如 PRIMARY KEYUNIQUE。 你可以通过 + 号在需要的字段上加上这些修饰符。

使用 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() 方法直接传入参数,更加直观,方便。

db.insert("User",
    "id" to 42,
    "name" to "John",
    "email" to "user@domain.org"
)

或者

database.use {
    insert("User",
            "id" to 42,
            "name" to "John",
            "email" to "user@domain.org")
}

方法 insertOrThrow(), replace(), replaceOrThrow() 都存在并具有相同的功能。

通过查看源码就能知道,Anko 只是对 这些方法做了一层包装而已,所以功能并没有任何变化。

查询数据

Anko 提供了一个方便的查询生成器。 可以使用 db.select(tableName, vararg columns) 创建,其中 dbSQLiteDatabase 对象。

方法 描述
column(String) 添加查询的列
distinct(Boolean) Distinct 查询
whereArgs(String) where(需要自己拼接字符串)
whereArgs(String, args) where,可自定义参数占位符
whereSimple(String, args) where,参数占位符是 ?
orderBy(String, [ASC/DESC]) 指定排序的列
groupBy(String) 指定分组的列
limit(count: Int) 设置查询结果的条目数
limit(offset: Int, count: Int) 设置查询结果的条目数(列:limit(5,10) 跳过查询结果前 5 条,然后取10条)
having(String) 指定 having 表达式(需要自己拼接字符串)
having(String, args) 指定 having 表达式和参数

用 ⭐ 标记的方法,参数是通过占位符一一对应的,所以参数的顺序可以打乱,下面是一个列子:

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 方法就能获取到查询结果了,exec 中扩展了 Cursor 对象(Cursor.() -> T),并且 exec 执行完之后自动释放资源。

database.use {
    select("User")
            .column("email")
            .exec {
                while (moveToNext()){
                    info("email:"+getString(0))
                }
            }
}

虽然方便了不少,但是还要操作 Cursor,差评!
说好的不使用 Cursor 呢,且往下看。

解析查询结果

为了彻底摆脱 Cursor 的阴影,Anko 提供了 parseSingle, parseOptparseList 三个方法。

方法 描述
parseSingle(rowParser): T 结果为一条数据
parseOpt(rowParser): T? 结果为一条数据或者没有数据
parseList(rowParser): List<T> 结果为多条数据

注意哦,如果使用 parseSingle() 或者 parseOpt() ,当结果是多条数据的时候会抛出异常哦。

那么问题来了,什么是 rowParser
每个方法都支持两种 parsers: RowParserMapRowParser

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

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

如果追求效率,请使用 RowParser (如果是多列,你必须要知道每列的索引)。 parseRow 实际上只支持 Long, Double, StringByteArray 。而 MapRowParser 可以通过列名来获取值。

Anko 提供了常用的单列解析器(结果只有一列的时候可用):

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

以 StringParser 为例,用法如下:

//查询所有用户的邮箱
database.use {
    select("User")
            .column("email")
            .whereArgs("name = {userName}","userName" to "jack")
            .parseList(StringParser)
            .forEach {
                info { "email:$it" }
            }
}

当查询结果为多列的时候可以用 MapRowParser。
首先要定义个数据类。

data class  User(val id:Long,val name:String,val email:String)

然后自定义一个 MapRowParser。

class UserRowParser : MapRowParser<User> {
    override fun parseRow(columns: Map<String, Any?>): User {
        return User(columns["id"] as Long, columns["name"] as String, columns["email"] as String)
    }
}

最后使用

database.use {
    select("User")
            .parseList(UserRowParser())
            .forEach {
                info { it }
            }
}

虽然目的达到了,但是还是有点麻烦对不对。
不用担心,Anko 早已看穿了这一切,使用 classParser 连 UserRowParser 都不需要了:

database.use {
    select("User")
            .parseList(classParser<User>())
            .forEach {
                info { it }
            }
}

自定义rowParsers

自定义 rowParsers 同自定义 MapRowParser(区别只在于前者需要知道数据的索引,后者需要知道列名):

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)
    }
}

使用 lambda 表达式实现:

database.use {
    select("User")
            .parseList(rowParser { id: Long, name: String, email: String -> User(id, name, email) })
            .forEach {
                info { it }
            }
}

Cursor streams

Anko 还提供了两种方式解析 Cursor ,转换成 数组列表 或者 map 列表(cursor.asSequence()cursor.asMapSequence()):

database.use {
    select("User")
            .exec {
                for (item in asSequence()) {
                    info("id=${item[0]},name=${item[1]},email=${item[2]}")
                }
            }
}

database.use {
    select("User")
            .exec {
                for (item in asMapSequence()) {
                    info("id=${item["id"]},name=${item["name"]},email=${item["email"]}")
                }
            }
}

修改数据

修改某一个用户的名字:

database.use {
    update("User", "name" to "zhangsan")
            .whereArgs("id = {userId}", "userId" to 123)
            .exec()
}

whereSimple() 方法用法:

database.use {
    update("User", "name" to "zhangsan")
            .whereSimple("id = ?",123.toString())
            .exec()
}

事务

transaction() 方法封装了事务,在其中发生错误时,方法中所有对数据库的操作都会回滚,如果没有发生任何错误,事务将被提交:

database.use {
    transaction {
        update("User", "name" to "lisi")
                .whereSimple("id = ?",123.toString())
                .exec()
        throw Exception("error")
        //抛出异常,程序崩溃,数据将修改失败
    }
}

如果要由于某种原因需要中止事务,只需抛出TransactionAbortException。这个异常只会终止事务,程序不会崩溃,当然所有事务中对数据库的操作也无效。

database.use {
    transaction {
        update("User", "name" to "lisi")
                .whereSimple("id = ?",123.toString())
                .exec()
        throw TransactionAbortException()
        //后面的代码都不会在执行了
        //....................
    }
}

本篇文章相关代码传送门

info/jiuyou/learnanko/sqlite/