资讯专栏INFORMATION COLUMN

Kotlin入门(26)数据库ManagedSQLiteOpenHelper

番茄西红柿 / 1554人阅读

摘要:共享参数毕竟只能存储简单的键值对数据,如果需要存取更复杂的关系型数据,就要用到数据库了。但是,系统自带的有个先天缺陷,就是它并未封装数据库管理类,这造成一个后果开发者需要在操作表之前中手工打开数据库连接,然后在操作结束后手工关闭数据库连接。

共享参数毕竟只能存储简单的键值对数据,如果需要存取更复杂的关系型数据,就要用到数据库SQLite了。尽管SQLite只是手机上的轻量级数据库,但它麻雀虽小、五脏俱全,与Oracle一样存在数据库的创建、变更、删除、连接等DDL操作,以及数据表的增删改查等DML操作,因此开发者对SQLite的使用编码一点都不能含糊。当然,Android为了方便开发者的工作,已经提供了一个操作SQLite的工具类即SQLiteOpenHelper,在App开发时可由SQLiteOpenHelper派生出具体的业务表管理类。
但是,系统自带的SQLiteOpenHelper有个先天缺陷,就是它并未封装数据库管理类SQLiteDatabase,这造成一个后果:开发者需要在操作表之前中手工打开数据库连接,然后在操作结束后手工关闭数据库连接。可是手工开关数据库连接存在着诸多问题,比如数据库连接是否重复打开了?数据库连接是否忘记关闭了?在A处打开数据库却在B处关闭数据是否造成业务异常?以上的种种问题都制约了SQLiteOpenHelper的安全性。
有鉴于此,Kotlin结合Anko库推出了改良版的SQLite管理工具,名叫ManagedSQLiteOpenHelper,该工具封装了数据库连接的开关操作,使得开发者完全无需关心SQLiteDatabase在何时在何处调用,也就避免了手工开关数据库连接可能导致的各种异常。同时ManagedSQLiteOpenHelper的用法与SQLiteOpenHelper几乎一模一样,唯一的区别是:数据表的增删改查语句需要放在use语句块之中,具体格式如下:

    use {
        //1、插入记录
        //insert(...)
        //2、更新记录
        //update(...)
        //3、删除记录
        //delete(...)
        //4、查询记录
        //query(...)或者rawQuery(...)
    }

 

其中表的查询操作还要借助于SQLite已有的游标类Cursor来实现,上述代码中的query和rawQuery方法,返回的都是Cursor对象,那么获取查询结果就得根据游标的指示一条一条遍历结果集合。下面是Cursor类的常用方法:
1、游标控制类方法,用于指定游标的状态:
close : 关闭游标
isClosed : 判断游标是否关闭
isFirst : 判断游标是否在开头
isLast : 判断游标是否在末尾
2、游标移动类方法,把游标移动到指定位置:
moveToFirst : 移动游标到开头
moveToLast : 移动游标到末尾
moveToNext : 移动游标到下一个
moveToPrevious : 移动游标到上一个
move : 往后移动游标若干偏移量
moveToPosition : 移动游标到指定位置
3、获取记录类方法,可获取记录的数量、类型以及取值。
getCount : 获取记录数
getInt : 获取指定字段的整型值
getFloat : 获取指定字段的浮点数值
getString : 获取指定字段的字符串值
getType : 获取指定字段的字段类型
接下来以用户注册信息数据库为例,看看Kotlin的数据库操作代码是怎样实现的,具体的实现代码示例如下:

class UserDBHelper(var context: Context, private var DB_VERSION: Int=CURRENT_VERSION) : ManagedSQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
    companion object {
        private val TAG = "UserDBHelper"
        var DB_NAME = "user.db" //数据库名称
        var TABLE_NAME = "user_info" //表名称
        var CURRENT_VERSION = 1 //当前的最新版本,如有表结构变更,该版本号要加一
        private var instance: UserDBHelper? = null
        @Synchronized
        fun getInstance(ctx: Context, version: Int=0): UserDBHelper {
            if (instance == null) {
                //如果调用时没传版本号,就使用默认的最新版本号
                instance = if (version>0) UserDBHelper(ctx.applicationContext, version)
                            else UserDBHelper(ctx.applicationContext)
            }
            return instance!!
        }
    }

    override fun onCreate(db: SQLiteDatabase) {
        Log.d(TAG, "onCreate")
        val drop_sql = "DROP TABLE IF EXISTS $TABLE_NAME;"
        Log.d(TAG, "drop_sql:" + drop_sql)
        db.execSQL(drop_sql)
        val create_sql = "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
            "_id INTEGER PRIMARY KEY  AUTOINCREMENT NOT NULL," +
            "name VARCHAR NOT NULL," + "age INTEGER NOT NULL," +
            "height LONG NOT NULL," + "weight FLOAT NOT NULL," +
            "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL" +
            //演示数据库升级时要先把下面这行注释
            ",phone VARCHAR" + ",password VARCHAR" + ");"
        Log.d(TAG, "create_sql:" + create_sql)
        db.execSQL(create_sql)
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        Log.d(TAG, "onUpgrade oldVersion=$oldVersion, newVersion=$newVersion")
        if (newVersion > 1) {
            //Android的ALTER命令不支持一次添加多列,只能分多次添加
            var alter_sql = "ALTER TABLE $TABLE_NAME ADD COLUMN phone VARCHAR;"
            Log.d(TAG, "alter_sql:" + alter_sql)
            db.execSQL(alter_sql)
            alter_sql = "ALTER TABLE $TABLE_NAME ADD COLUMN password VARCHAR;"
            Log.d(TAG, "alter_sql:" + alter_sql)
            db.execSQL(alter_sql)
        }
    }

    fun delete(condition: String): Int {
        var count = 0
        use {
            count = delete(TABLE_NAME, condition, null)
        }
        return count
    }

    fun insert(info: UserInfo): Long {
        val infoArray = mutableListOf(info)
        return insert(infoArray)
    }

    fun insert(infoArray: MutableList): Long {
        var result: Long = -1
        for (i in infoArray.indices) {
            val info = infoArray[i]
            var tempArray: List
            // 如果存在同名记录,则更新记录
            // 注意条件语句的等号后面要用单引号括起来
            if (info.name.isNotEmpty()) {
                val condition = "name=${info.name}"
                tempArray = query(condition)
                if (tempArray.size > 0) {
                    update(info, condition)
                    result = tempArray[0].rowid
                    continue
                }
            }
            // 如果存在同样的手机号码,则更新记录
            if (info.phone.isNotEmpty()) {
                val condition = "phone=${info.phone}"
                tempArray = query(condition)
                if (tempArray.size > 0) {
                    update(info, condition)
                    result = tempArray[0].rowid
                    continue
                }
            }
            // 不存在唯一性重复的记录,则插入新记录
            val cv = ContentValues()
            cv.put("name", info.name)
            cv.put("age", info.age)
            cv.put("height", info.height)
            cv.put("weight", info.weight)
            cv.put("married", info.married)
            cv.put("update_time", info.update_time)
            cv.put("phone", info.phone)
            cv.put("password", info.password)
            use {
                result = insert(TABLE_NAME, "", cv)
            }
            // 添加成功后返回行号,失败后返回-1
            if (result == -1L) {
                return result
            }
        }
        return result
    }

    @JvmOverloads
    fun update(info: UserInfo, condition: String = "rowid=${info.rowid}"): Int {
        val cv = ContentValues()
        cv.put("name", info.name)
        cv.put("age", info.age)
        cv.put("height", info.height)
        cv.put("weight", info.weight)
        cv.put("married", info.married)
        cv.put("update_time", info.update_time)
        cv.put("phone", info.phone)
        cv.put("password", info.password)
        var count = 0
        use {
            count = update(TABLE_NAME, cv, condition, null)
        }
        return count
    }

    fun query(condition: String): List {
        val sql = "select rowid,_id,name,age,height,weight,married,update_time,phone,password from $TABLE_NAME where $condition;"
        Log.d(TAG, "query sql: " + sql)
        var infoArray = mutableListOf()
        use {
            val cursor = rawQuery(sql, null)
            if (cursor.moveToFirst()) {
                while (true) {
                    val info = UserInfo()
                    info.rowid = cursor.getLong(0)
                    info.xuhao = cursor.getInt(1)
                    info.name = cursor.getString(2)
                    info.age = cursor.getInt(3)
                    info.height = cursor.getLong(4)
                    info.weight = cursor.getFloat(5)
                    //SQLite没有布尔型,用0表示false,用1表示true
                    info.married = if (cursor.getInt(6) == 0) false else true
                    info.update_time = cursor.getString(7)
                    info.phone = cursor.getString(8)
                    info.password = cursor.getString(9)
                    infoArray.add(info)
                    if (cursor.isLast) {
                        break
                    }
                    cursor.moveToNext()
                }
            }
            cursor.close()
        }
        return infoArray
    }

    fun queryByPhone(phone: String): UserInfo {
        val infoArray = query("phone=$phone")
        val info: UserInfo = if (infoArray.size>0) infoArray[0] else UserInfo()
        return info
    }

    fun deleteAll(): Int = delete("1=1")

    fun queryAll(): List = query("1=1")

}

因为ManagedSQLiteOpenHelper来自于Anko库,所以记得在UserDBHelper文件头部加上下面一行导入语句:

import org.jetbrains.anko.db.ManagedSQLiteOpenHelper

 

另外,有别于常见的anko-common包,Anko库把跟数据库有关的部分放到了anko-sqlite包中,故而还需修改模块的build.gradle文件,在dependencies节点中补充下述的anko-sqlite包编译配置:

    compile "org.jetbrains.anko:anko-sqlite:$anko_version"

 

现在有了用户信息表的管理类,在Activity代码中存取用户信息就方便多了,下面是往数据库存储用户信息和从数据库读取用户信息的代码片段:

    var helper: UserDBHelper = UserDBHelper.getInstance(this)
    //往数据库存储用户信息
    btn_save.setOnClickListener {
        when (true) {
            et_name.text.isEmpty() -> toast("请先填写姓名")
            et_age.text.isEmpty() -> toast("请先填写年龄")
            et_height.text.isEmpty() -> toast("请先填写身高")
            et_weight.text.isEmpty() -> toast("请先填写体重")
            else -> {
                val info = UserInfo(name = et_name.text.toString(),
                age = et_age.text.toString().toInt(),
                height = et_height.text.toString().toLong(),
                weight = et_weight.text.toString().toFloat(),
                married = bMarried,
                update_time = DateUtil.nowDateTime)
                helper.insert(info)
                toast("数据已写入SQLite数据库")
            }
        }
    }
    
    //从数据库读取用户信息
    private fun readSQLite() {
        val userArray = helper.queryAll()
        var desc = "数据库查询到${userArray.size}条记录,详情如下:"
        for (i in userArray.indices) {
            val item = userArray[i]
            desc = "$descn第${i+1}条记录信息如下:" +
                    "n 姓名为${item.name}" +
                    "n 年龄为${item.age}" +
                    "n 身高为${item.height}" +
                    "n 体重为${item.weight}" +
                    "n 婚否为${item.married}" +
                    "n 更新时间为${item.update_time}"
        }
        if (userArray.isEmpty()) {
            desc = "数据库查询到的记录为空"
        }
        tv_sqlite.text = desc
    }

  

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/2674.html

相关文章

  • Android Gradle系列-进阶篇

    摘要:如果你有新建一个项目的经历,那么你将看到推荐的方案在的中使用来定义版本号全局变量。例如之前的版本号就可以使用如下方式实现因为使用的是语言,所以以上都是语法例如版本控制,上面代码的意思就是将有个相关的版本依赖放到的变量中,同时放到了中。 showImg(https://segmentfault.com/img/bVbsh3m?w=2560&h=1280); 上篇文章我们已经将Gradle...

    endless_road 评论0 收藏0
  • Android Gradle系列-进阶篇

    摘要:如果你有新建一个项目的经历,那么你将看到推荐的方案在的中使用来定义版本号全局变量。例如之前的版本号就可以使用如下方式实现因为使用的是语言,所以以上都是语法例如版本控制,上面代码的意思就是将有个相关的版本依赖放到的变量中,同时放到了中。 showImg(https://segmentfault.com/img/bVbsh3m?w=2560&h=1280); 上篇文章我们已经将Gradle...

    lvzishen 评论0 收藏0
  • Kotlin入门(32)网络接口访问

    摘要:将经纬度转换为详细地址,就要访问谷歌地图提供的地址查询接口了,该接口的地址形如请求参数信息,把经纬度数据作文请求参数传入,对方会返回一个包含地址信息的串,通过解析串即可获得当前的详细地址。手机上的资源毕竟有限,为了获取更丰富的信息,就得到辽阔的互联网大海上冲浪。对于App自身,也要经常与服务器交互,以便获取最新的数据显示到界面上。这个客户端与服务端之间的信息交互,基本使用HTTP协议进行通信...

    mingzhong 评论0 收藏0
  • Kotlin入门(21)活动页面的跳转处理

    摘要:的活动页面跳转是最常用的功能之一,在前几章的源码中便多次见到了,常常是点击界面上的某个按钮,然后跳转到与之对应的下一个页面。Activity的活动页面跳转是App最常用的功能之一,在前几章的demo源码中便多次见到了,常常是点击界面上的某个按钮,然后跳转到与之对应的下一个页面。对于App开发者来说,该功能的实现非常普通,使用Java编码不过以下两行代码而已: Intent inte...

    BlackFlagBin 评论0 收藏0
  • Kotlin入门(31)JSON字符串的解析

    摘要:获取指定名称的字符串。把当前输出为一个字符串。获取数组对象在指定位置处的对象。下面直接给出解析串的常用代码片段,包括如何构造串如何解析串,以及如何遍历串构造串地址信息第个元素这是测试串解析串表示的范围是左闭右开区间。json是App进行网络通信最常见的数据交互格式,Android也自带了json格式的处理工具包org.json,该工具包主要提供了JSONObject(json对象)与JSON...

    Moxmi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<