SQLite for Android

      在〈SQLite for Android〉中尚無留言

SQLite 是本地端的資料庫,把收集到的資料儲存成一個檔案,存放在本機的儲存裝置中

SQLite 不是一般的文字檔,而是使用資料庫的格式來儲存,所以新增或查詢的效能比一般的文字檔高出甚多。

SQLite 跟其它的資料庫一樣,使用二元樹演算法,不必讀進全部的內容,就可以高速搜詢所有資料。

SQLite 只允許本機

在手機的世界中,網路時好時斷。當沒有網路時,總不能把手機即時收集到的資料,卻因為沒有網路不能傳送,就把資料丟掉吧!! 那該怎麼辦呢? 這時 SQLite 就有用處囉。

SQLite 只允許在本機存取,沒有網路斷線的問題,會與手機永遠連線。所以就可以把手機收集到的資訊先存在 SQLite 中,然後再用一個新的執行緒來檢查網路是否正常,若恢復連線,再由 SQLite 取得資料然後傳送到遠端伺服器。

會員登入

會員登入系統當然就不能使用 SQLite了。想想看,在你手機註冊會員資料,而且註冊成功了。那麼可以在別人的手機上登入嗎?? 當然不可能,因為註冊的會員資料只有你的手機才有。除非登入者用你的手機登入。

那~~~那麼多的 app 是怎麼作出來的? 在我的手機註冊成功,然後可以在別人的手機登入? 請看下圖

手機 A 註冊成功的資料,會經由網路傳送並儲存到 xxx 公司的 MySQL 伺服器。
然後在手機 B 登入時,也要由 xxx 公司的 MySQL 伺服器驗証並取得資料。

建立開啟資料庫

使用 context. openOrCreateDatabase() 建立資料庫。如果資料庫已存在,則只會進行開啟的動作。

第一個參數為資料庫的檔案名稱。

第二個參數為權限,權限有三種,分別如下

1. MODE_PRIVATE : 只有這個 app 可以存取。
2. MODE_WORLD_READABLE : 所有的 APP 都可以來讀取。
3. MODE_WORLD_WRITEABLE : 所有的 APP 都可以來寫入。底下傳入 Context.MODE_PRIVATE設定權限。

第三個參數為 ErrorHandler,如果不打算處理 Error 的情況,就塞 null 進去,或是不要放第三個參數。

底下的 db 變數為 SQLiteDatabase 型態

val db= this.openOrCreateDatabase("local.db", Context.MODE_PRIVATE, null )

請特別注意一點,當資料庫不再使用時,需使用 close()將資料庫關閉,否則會出現 IllegalStateException 例外。

db.close()

刪除資料庫

使用 context的 deleteDatabase(“資料庫檔名”)。如果原本並無此檔名,使用此方法刪除也不會出錯

this.deleteDatabase("local.db")

建立資料表

資料庫建立並開啟後,接下來就可以建立資料表了。使用 create table 的 SQL語法產生命令字串,再將此字串交由 db.execSQL()來執行即可。

建立資料表時,要指定每個欄位的資料型態,SQLite只有四種資料型態,分別為 INTEGER, REAL, TEXT, BLOB。

底下我們把 id 設為 autoincrement,每次新增一筆資料,其 id 值就會自動加 1。account 欄位設為 UNIQUE(唯一),若有 account 的值重複,則會出現 SQLiteConstraintException 例外

val strMember=String.format(
    "create table member (" +
            "id integer primary key autoincrement, " +  //0
            "account TEXT UNIQUE, " +  //1
            "password TEXT, " +  //2
            "mail TEXT)"
)
db.execSQL(strMember)

刪除資料表

刪除資料表,同樣是使用SQL語法。底下是刪除 member 資料表的方法

db.execSQL("drop table if exists member")

CRUD

資料庫不外乎就是四個動作,分別為新增(Create),查詢(Retrieve),更新(Update) 和刪除(Delete)。

新增、更新及刪除三個動作的SQL語法,必需由 execSQL()方法來執行。至於查詢的SQL語法,需由 rawQuery()方法來執行。

execQuery

比如想新增一筆資料,可以先把 SQL 寫在 cmd 字串中,再交由 execQuery() 來執行。底下使用 try-catch 將 execSQL包含起來,是為了防止帳號重複所產生的 SQLiteConstraintException 例外。

val account="thomas"
val password="123456"
val mail="mahaljsp@gmail.com"
var cmd=String.format("insert into member (account, password, mail) values ('${account}', '${password}','${mail}')")
try {
    db.execSQL(cmd)
}
catch(e: SQLiteConstraintException){
    Log.d("Thomas", e.message.toString())
}

rawQuery

若要查詢資料,可以把查詢的SQL語法寫在 cmd字串中,再由 rawQuery執行。執行後的所有資料集會置於Cursor 資料型態物件 (cursor),再由 cursor.moveToNext()一筆一筆讀取。每讀取一筆,再由 cursor.getString(欄位索引) 讀取每一個欄位的資料。第一個欄位的索引為 0。

var cmd = String.format("select * from member")
var cursor = db.rawQuery(cmd, null)
while (cursor.moveToNext()) {
    Log.d("Thomas",
cursor.getString(1) + ":" + cursor.getString(2) +
":" + cursor.getString(2) ) }

總整理

上述都是片斷的指令說明,實際上要讓初學者整合起來,其實並不容易,所以底下列出整合後的完整代碼

package com.asuscomm.mahaljsp.sqlite_test
import android.content.Context
import android.database.sqlite.SQLiteConstraintException
import android.database.sqlite.SQLiteDatabase
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//建立或開啟資料庫
val db:SQLiteDatabase= this.openOrCreateDatabase("local.db", Context.MODE_PRIVATE, null ) //建立資料表的SQL語法 val strMember=String.format( "create table member (" + "id integer primary key autoincrement, " + //0 "account TEXT UNIQUE, " + //1 "password TEXT, " + //2 "mail TEXT)" ) //防止第二次執行時,重複建立資料表 var cursor=db.rawQuery(
"select name from sqlite_master where type='table' and name='member' ",
null) if(!cursor.moveToNext()){ db.execSQL(strMember) } //新增一筆資料 val account="thomas" val password="123456" val mail="mahaljsp@gmail.com" var cmd=String.format("insert into member (account, password, mail) values ('${account}', '${password}','${mail}')") try { db.execSQL(cmd) } catch(e: SQLiteConstraintException){ Log.d("Thomas", e.message.toString()) } //查詢資料表 cmd = String.format("select * from member") cursor = db.rawQuery(cmd, null) while (cursor.moveToNext()) { Log.d("Thomas",
cursor.getString(1) + ":" +
cursor.getString(2) + ":" +
cursor.getString(3) ) } db.close() } }

防止重複建立資料表

每次執行app時,都要建立資料庫及資料表。重複建立資料庫是沒問題的,因為如果原先並沒有 local.db,就會建立新的資料庫。但如果經有local.db,再次下達 openOrCreateDatabase()只會開啟資料庫,並不會重新建立。

不過 create table member 建立 member 資料表時,第二次達行時又再重建一次,此時就會出現資料表已存在的例外。所以為了避免第二次重複建立 member,就必需使用如下SQL 語法查詢是否已存在 member。

"select name from sqlite_master where type='table' and name='member'"

經上述查詢後,如果 member 不存在,才會建立資料表。

自訂LocaldbHelper

前面的說明中,要開資料庫,建立資料表,還要檢查是否第二次執行重複建立資料表的問題,實在是煩人。所以Android幫我們建立了一個SQLiteHelper的類別,方便我們操作。

首先在專案下新增 LocaldbHelper 類別。

此類別繼承了 SQLiteOpenHelper 類別,裏面有三個重要的類別變數,分別為

DATABASE_NAME : 資料庫檔名
VERSION : 資料庫版本,系統會自動檢查版本,如果下次執行時的版本高於上一次,就會執行 onUpgrade方法,此方法會刪除原本的資料表,重新建立新的資料表
strMember : 建立資料表的 SQL 語法字串

底下是 LocaldbHelper 的完整代碼,只要更改藍色及紅色的部份即可

package com.asuscomm.mahaljsp.sqlite_test
import android.content.Context
import android.database.SQLException
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

class LocaldbHelper(context: Context, name: String, factory: SQLiteDatabase.CursorFactory?, version: Int) :
    SQLiteOpenHelper(context,name,factory,version) {
    companion object{
        val DATABASE_NAME = "local.db"
        val VERSION = 1
        var database:SQLiteDatabase? = null
        val strMember=String.format(
            "create table member (" +
                    "id integer primary key autoincrement, " +  //0
                    "account TEXT UNIQUE, " +  //1
                    "password TEXT, " +  //2
                    "mail TEXT)" //3
        )
        fun getDatabase(context: Context?): SQLiteDatabase? {
            if (database == null || !database!!.isOpen) {
                database = LocaldbHelper(context!!, DATABASE_NAME, null, VERSION).writableDatabase
            }
            return database
        }
    }

    override fun onCreate(db: SQLiteDatabase?) {
        try {
            db?.execSQL(strMember)
        }
        catch (e: SQLException){
            onUpgrade(db, 0, 0)
        }
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        db?.execSQL("DROP TABLE IF EXISTS member")
        onCreate(db)
    }
}

Localdb

請在專案下新增 Localdb類別。

Localdb 類別裏的方法都是類別方法,不需產生此物件就可以在任何地方執行裏面的類別方法。

package com.asuscomm.mahaljsp.sqlite_test

import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase

object Localdb {
    var db :SQLiteDatabase ?= null
    fun open(context: Context){
        db=LocaldbHelper.getDatabase(context)
    }
    fun execSQL(cmd:String){
        db?.execSQL(cmd)
    }
    fun rawQuery(cmd:String):Cursor?{
        val cursor=db?.rawQuery(cmd, null)
        return cursor
    }
    fun close(){
        db?.close()
    }
}

應用

上面二個類別都準備好了,就可以在 MainActivity 裏使用如下代碼。

1.  Localdb.open : 一定要先開啟資料庫
2. Localdb.execSQL/Localdb.rawQuery : 開始執行SQL命令
3. Localdb.close : 最後一定要關閉資料庫

package com.asuscomm.mahaljsp.sqlite_test
import android.database.sqlite.SQLiteConstraintException
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Localdb.open(this)
        val account="shenny"
        val password="123456"
        val mail="mahaljsp@gmail.com"
        var cmd=String.format("insert into member (account, password, mail) values ('${account}', '${password}','${mail}')")
        
//新增一筆資料 try { Localdb.execSQL(cmd) } catch(e: SQLiteConstraintException){ Log.d("Thomas", e.message.toString()) } //查詢資料表 cmd = String.format("select * from member") var cursor = Localdb.rawQuery(cmd) while (cursor?.moveToNext()==true) { Log.d("Thomas", cursor.getString(1) + ":" + cursor.getString(2) + ":" + cursor.getString(3) ) } Localdb.close() } }

多執行緒

依官網的說明,一個數據庫連線不能被多個執行緒同時使用。

所以最好使用 Handler 交由 UI 主執行緒來操作,不然就是新增一個專門用來操作資料庫的新執行緒,千萬別把資料庫的操作分散在不同的執行緒中。

ContentValues

Android 提供 db.insert,db.update 等方法,這些方法必需搭配 ContentValues 物件,將資料置入 cv 中,再使用 Localdb.db?.update() 等方法。代碼如下

val cv = ContentValues()
val id=1
cv.put("name", "張大呆") cv.put("account", "thomas")
cv.put("mail", "abc@gmail.com") Localdb.db?.update("member", cv, "id=${id}", null)

在此不建議使用此方法,這些方法是讓不懂 SQL 語法的人用的。使用 ContentValues 不但靈活性不佳,且好像遇到錯誤時,無法攔截例外處理。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *