1. 程式人生 > >Kotlin入門(26)資料庫ManagedSQLiteOpenHelper

Kotlin入門(26)資料庫ManagedSQLiteOpenHelper

共享引數畢竟只能儲存簡單的鍵值對資料,如果需要存取更復雜的關係型資料,就要用到資料庫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<UserInfo>): Long {
        var result: Long = -1
        for (i in infoArray.indices) {
            val info = infoArray[i]
            var tempArray: List<UserInfo>
            // 如果存在同名記錄,則更新記錄
            // 注意條件語句的等號後面要用單引號括起來
            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<UserInfo> {
        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<UserInfo>()
        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<UserInfo> = 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 = "$desc\n第${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
    }

 

點此檢視Kotlin入門教程的完整目錄


__________________________________________________________________________
開啟微信掃一掃下面的二維碼,或者直接搜尋公眾號“老歐說安卓”新增關注,更快更方便地閱讀技術乾貨。