概述
Sqlite數(shù)據(jù)庫(kù)是Android系統(tǒng)內(nèi)常用的數(shù)據(jù)存儲(chǔ)的方式之一,還有其他幾種存儲(chǔ)方式:文件存儲(chǔ),SP存儲(chǔ)等。
SQLite是一個(gè)進(jìn)程內(nèi)的輕量級(jí)嵌入式數(shù)據(jù)庫(kù),它的數(shù)據(jù)庫(kù)就是一個(gè)文件,實(shí)現(xiàn)了自給自足、無(wú)服務(wù)器、零配置的、事務(wù)性的SQL數(shù)據(jù)庫(kù)引擎。它是一個(gè)零配置的數(shù)據(jù)庫(kù),這就體現(xiàn)出來(lái)SQLite與其他數(shù)據(jù)庫(kù)的最大的區(qū)別:SQLite不需要在系統(tǒng)中配置,直接可以使用。且SQLite不是一個(gè)獨(dú)立的進(jìn)程,可以按應(yīng)用程序需求進(jìn)行靜態(tài)或動(dòng)態(tài)連接。SQLite可直接訪問(wèn)其存儲(chǔ)文件。
Sqlite具有如下特點(diǎn):
- 存儲(chǔ)結(jié)構(gòu)型,關(guān)系型數(shù)據(jù)
- 支持SQL語(yǔ)言
- 支持事務(wù)處理
- 獨(dú)立,無(wú)需服務(wù)進(jìn)程
使用
使用SQLite實(shí)現(xiàn)需要了解SQLiteOpenHelper數(shù)據(jù)庫(kù)工具類。
SQLiteOpenHelper是一個(gè)在Android中使用的Sqlite數(shù)據(jù)庫(kù)輔助操作的工具類,可以用來(lái)管理數(shù)據(jù)庫(kù)操作(增刪改查)以及版本控制。
使用Sqlite數(shù)據(jù)庫(kù)主要包括如下幾個(gè)步驟:
- 創(chuàng)建SQLiteOpenHelper的子類,用來(lái)管理數(shù)據(jù)表的創(chuàng)建,數(shù)據(jù)庫(kù)版本控制,根據(jù)需求實(shí)現(xiàn)其中的方法。
- 新建SQLiteDBManager數(shù)據(jù)庫(kù)工具類用來(lái)承載數(shù)據(jù)庫(kù)的增刪改查操作。
接下來(lái)對(duì)上述兩個(gè)步驟進(jìn)行簡(jiǎn)單說(shuō)明。
一、 SQliteOpenHelper
首先我們需要?jiǎng)?chuàng)建一個(gè)SQLiteOpenHelper的子類,作為數(shù)據(jù)庫(kù)創(chuàng)建的輔助工具類。在此工具類中有兩個(gè)重要方法:onCreate
和onUpgrade
方法。
onCreate
是創(chuàng)建方法,部分表的創(chuàng)建可以在此實(shí)現(xiàn),通過(guò)SQL語(yǔ)句以及execSQL()
的方法可以很快的就能夠?qū)崿F(xiàn)。
onUpgrade
是用來(lái)控制數(shù)據(jù)庫(kù)升級(jí)的,在此方法中可以控制數(shù)據(jù)庫(kù)版本的變動(dòng)。
升級(jí)方法中需要對(duì)于版本的升級(jí)降級(jí)的每一項(xiàng)操作都要包含到,以免造成錯(cuò)誤。
示例代碼如下:
class DataBaseHelper(
context: Context?, name: String?, factory: SQLiteDatabase.CursorFactory?,
version: Int, errorHandler: DatabaseErrorHandler?
) : SQLiteOpenHelper(context, name, factory, version, errorHandler) {
override fun onCreate(db: SQLiteDatabase?) {
var sqlCreate =
"create table " + TEST_TABLE_NAME + "(" + TablePerson.ID_COLUMN + " integer primary key autoincrement," +
TablePerson.NAME_COLUMN + " varchar(64))"
db?.execSQL(sqlCreate)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
// 使用 SQL的ALTER語(yǔ)句
LogUtil.instance.d("onUpgrade,newVersion is $newVersion")
if (db == null)
return
db.beginTransaction() //加入事務(wù)
try {
for (j in oldVersion..newVersion) {
when (j) {
2 -> {
var sql =
"alter table $TEST_TABLE_NAME add other varchar(64)"
LogUtil.instance.d(sql)
db?.execSQL(sql)
}
3 -> {
}
else -> {
}
}
}
db.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.instance.d("Monicat:SQLiteDatabase upgrade failed.")
} finally {
db.endTransaction()
}
}
companion object {
const val TEST_TABLE_NAME = "person"
}
}
上面對(duì)于SQL表的操作加入和事務(wù),使得操作具有原子性和持久性。
除此之外,還需要初始化數(shù)據(jù)庫(kù),也就是創(chuàng)建一個(gè)SQliteOpenHelper
對(duì)象,示例如下:
/**
* 創(chuàng)建數(shù)據(jù)庫(kù)對(duì)象
*/
fun createDb(version: Int) {
var sqLiteHelper = DataBaseHelper(
context,
"sqlite_test",
null,
version,
null
)
sqLiteDB = sqLiteHelper.readableDatabase
//和read方法一樣都能夠獲得一個(gè)可讀寫(xiě)的數(shù)據(jù)庫(kù)對(duì)象,注意和read的區(qū)別
// sqLiteDB = sqLiteHelper.writableDatabase
}
通過(guò)SQliteOpenHelper
構(gòu)造函數(shù)聲明數(shù)據(jù)庫(kù)的版本和名稱等信息,然后通過(guò)getreadableDatabase
和writableDatabase
獲取到數(shù)據(jù)庫(kù)對(duì)象,之后在通過(guò)此數(shù)據(jù)庫(kù)對(duì)象進(jìn)行具體的數(shù)據(jù)表操作。
需要注意的是
readableDatabase
和writableDatabase
的區(qū)別,兩者雖然都能夠獲取到數(shù)據(jù)庫(kù)對(duì)象,但是在當(dāng)數(shù)據(jù)庫(kù)在磁盤(pán)滿了不能寫(xiě)入的情況下采用writableDatabase
是會(huì)打開(kāi)失敗報(bào)錯(cuò)的,而采用readableDatabase
只是會(huì)以只可讀的方式打開(kāi)數(shù)據(jù)庫(kù),不錯(cuò)直接報(bào)錯(cuò)。
定義具體的數(shù)據(jù)類,示例如下:
package com.example.demowork1.database.sqlite
class TablePerson(
var id: Int,
var name: String
) {
companion object {
const val ID_COLUMN = "id"
const val NAME_COLUMN = "name"
}
}
二、數(shù)據(jù)操作類
在定義了數(shù)據(jù)庫(kù)類之后,接下來(lái)我們就可以定義具體的數(shù)據(jù)表操作了,也就是增刪改查等數(shù)據(jù)操作。
在SQLite中,數(shù)據(jù)操作有兩種方式,一種是直接調(diào)用SQLiteDatabase
的方法,另一種是采用執(zhí)行SQL語(yǔ)句的方式。下面對(duì)增刪改查各種操作進(jìn)行逐一說(shuō)明。
1. 增加
增加可以通過(guò)調(diào)用SQLiteDatabase
的insert
方法進(jìn)行插入數(shù)據(jù),示例如下:
/**
* 使用insert方法添加數(shù)據(jù)
* @param:插入的數(shù)據(jù)
*/
fun insert1(person: TablePerson) {
sqLiteDB?.beginTransaction() ?: return
try {
if (checkExistData(person.id)) {
LogUtil.instance.toast("數(shù)據(jù)已經(jīng)存在", context)
} else {
var values = ContentValues()
values.put(TablePerson.ID_COLUMN, person.id)
values.put(TablePerson.NAME_COLUMN, person.name)
sqLiteDB?.insert(
DataBaseHelper.TEST_TABLE_NAME, null, values)
}
sqLiteDB?.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.instance.d("插入數(shù)據(jù)失敗")
} finally {
sqLiteDB?.endTransaction()
}
}
除此之外,還可以通過(guò)調(diào)用SQL語(yǔ)句的方法實(shí)現(xiàn)
/**
* 使用SQL語(yǔ)句添加數(shù)據(jù)
* @param person:插入的數(shù)據(jù)
* 注意需要在字符串添加''
*/
fun insert2(person: TablePerson) {
sqLiteDB?.beginTransaction() ?: return
try {
if (checkExistData(person.id)) {
LogUtil.instance.toast("數(shù)據(jù)已經(jīng)存在", context)
} else {
var insertSql =
"insert into " + DataBaseHelper.TEST_TABLE_NAME + " (" + TablePerson.ID_COLUMN + " , " +
TablePerson.NAME_COLUMN + ") values (" + person.id + " , '" + person.name + "')"
sqLiteDB?.execSQL(insertSql)
}
sqLiteDB?.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.instance.d("插入數(shù)據(jù)失敗")
} finally {
sqLiteDB?.endTransaction()
}
}
通過(guò)SQL語(yǔ)句進(jìn)行操作一般都是去調(diào)用execSQL
方法。
2. 更新
更新可以通過(guò)調(diào)用SQLiteDatabase
的update
方法進(jìn)行插入數(shù)據(jù),示例如下:
/**
* 使用update方法更新數(shù)據(jù)
* @param:更新的數(shù)據(jù)
*/
fun update1(person: TablePerson) {
sqLiteDB?.beginTransaction() ?: return
try {
if (checkExistData(person.id)) {
// a. 創(chuàng)建一個(gè)ContentValues對(duì)象
var values = ContentValues()
values.put(TablePerson.NAME_COLUMN, person.name)
// b. 調(diào)用update方法修改數(shù)據(jù)庫(kù):將id=1 修改成 name = zhangsan
sqLiteDB?.update(
DataBaseHelper.TEST_TABLE_NAME,
values,
TablePerson.ID_COLUMN + "=?",
arrayOf(person.id.toString())
)
// 參數(shù)1:表名(String)
// 參數(shù)2:需修改的ContentValues對(duì)象
// 參數(shù)3:WHERE表達(dá)式(String),需數(shù)據(jù)更新的行; 若該參數(shù)為 null, 就會(huì)修改所有行;?號(hào)是占位符
// 參數(shù)4:WHERE選擇語(yǔ)句的參數(shù)(String[]), 逐個(gè)替換 WHERE表達(dá)式中 的“?”占位符;
} else {
LogUtil.instance.toast("數(shù)據(jù)不存在", context)
}
sqLiteDB?.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.instance.d("更新數(shù)據(jù)失敗")
} finally {
sqLiteDB?.endTransaction()
}
}
當(dāng)然也可以通過(guò)調(diào)用SQL語(yǔ)句進(jìn)行實(shí)現(xiàn):
/**
* 使用SQL語(yǔ)句更新數(shù)據(jù)
* @param:更新的數(shù)據(jù)。
* 注意字符串添加''
*/
fun update2(person: TablePerson) {
sqLiteDB?.beginTransaction() ?: return
try {
if (checkExistData(person.id)) {
// 注:也可采用SQL語(yǔ)句修改
var updateSql =
"update " + DataBaseHelper.TEST_TABLE_NAME + " set " + TablePerson.NAME_COLUMN +
" = '" + person.name + "' where " + TablePerson.ID_COLUMN + " = " + person.id
sqLiteDB?.execSQL(updateSql)
} else {
LogUtil.instance.toast("數(shù)據(jù)不存在", context)
}
sqLiteDB?.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.instance.d("更新數(shù)據(jù)失敗")
} finally {
sqLiteDB?.endTransaction()
}
}
3. 刪除
更新可以通過(guò)調(diào)用SQLiteDatabase
的delete
方法進(jìn)行插入數(shù)據(jù),示例如下:
/**
* 使用delete方法刪除數(shù)據(jù)
* @param:刪除數(shù)據(jù)的ID
*/
fun delete1(id: Int) {
sqLiteDB?.beginTransaction() ?: return
try {
if (checkExistData(id)) {
sqLiteDB?.delete( //參數(shù)和update方法相似
DataBaseHelper.TEST_TABLE_NAME, TablePerson.ID_COLUMN + "=?",
arrayOf(id.toString())
)
} else {
LogUtil.instance.toast("數(shù)據(jù)不存在", context)
}
sqLiteDB?.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.instance.d("更新數(shù)據(jù)失敗")
} finally {
sqLiteDB?.endTransaction()
}
}
當(dāng)然也可以通過(guò)SQL語(yǔ)句實(shí)現(xiàn):
/**
* 使用SQL語(yǔ)句刪除數(shù)據(jù)
* @param:刪除數(shù)據(jù)的ID
*/
fun delete2(id: Int) {
sqLiteDB?.beginTransaction() ?: return
try {
if (checkExistData(id)) {
// 注:也可采用SQL語(yǔ)句修改
var deleteSql =
"delete from " + DataBaseHelper.TEST_TABLE_NAME + " where " +
TablePerson.ID_COLUMN + " = " + id
sqLiteDB?.execSQL(deleteSql)
} else {
LogUtil.instance.toast("數(shù)據(jù)不存在", context)
}
sqLiteDB?.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
LogUtil.instance.d("更新數(shù)據(jù)失敗")
} finally {
sqLiteDB?.endTransaction()
}
}
4. 查詢
由于查詢操作相對(duì)增刪改操作更加復(fù)雜,尤其是在查詢的條件和格式上都比較復(fù)雜,因此使用的SQL語(yǔ)句來(lái)實(shí)現(xiàn)查詢操作,調(diào)用SQLiteDatabase
的rawQuery
或者query
方法來(lái)實(shí)現(xiàn)結(jié)果。
query和rawQuery方法更多的是在參數(shù)上的不同,查看源碼可以發(fā)現(xiàn)二者最后調(diào)用的是同一個(gè)方法。
fun queryTest1(id: Int) {
val result =
sqLiteDB?.rawQuery("select * from person where id>?", arrayOf(id.toString())) ?: return
// var result = sqLiteDB?.query(DataBaseHelper.TEST_TABLE_NAME, arrayOf(TablePerson.ID_COLUMN, TablePerson.NAME_COLUMN),
// "id>?", arrayOf(id.toString()), null, null, null) ?: return
result.moveToFirst()
while (!result.isAfterLast) {
var mId: Int = result.getInt(0)
var mName: String = result.getString(1)
LogUtil.instance.d("id=$mId name$mName")
// do something useful with these
result.moveToNext()
}
result.close()
}
可以看到查詢的方式主要是游標(biāo)移動(dòng)查詢,游標(biāo)的方法有多種,部分示例如下,大家可以根據(jù)情況自行選擇:
/**
//Cursor對(duì)象常用方法如下:
c.move(int offset); //以當(dāng)前位置為參考,移動(dòng)到指定行
c.moveToFirst(); //移動(dòng)到第一行
c.moveToLast(); //移動(dòng)到最后一行
c.moveToPosition(int position); //移動(dòng)到指定行
c.moveToPrevious(); //移動(dòng)到前一行
c.moveToNext(); //移動(dòng)到下一行
c.isFirst(); //是否指向第一條
c.isLast(); //是否指向最后一條
c.isBeforeFirst(); //是否指向第一條之前
c.isAfterLast(); //是否指向最后一條之后
c.isNull(int columnIndex); //指定列是否為空(列基數(shù)為0)
c.isClosed(); //游標(biāo)是否已關(guān)閉
c.getCount(); //總數(shù)據(jù)項(xiàng)數(shù)
c.getPosition(); //返回當(dāng)前游標(biāo)所指向的行數(shù)
c.getColumnIndex(String columnName);//返回某列名對(duì)應(yīng)的列索引值
c.getString(int columnIndex); //返回當(dāng)前行指定列的值
// 方法說(shuō)明
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);
db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
// 參數(shù)說(shuō)明
// table:要操作的表
// columns:查詢的列所有名稱集
// selection:WHERE之后的條件語(yǔ)句,可以使用占位符
// groupBy:指定分組的列名
// having指定分組條件,配合groupBy使用
// orderBy指定排序的列名
// limit指定分頁(yè)參數(shù)
// distinct可以指定“true”或“false”表示要不要過(guò)濾重復(fù)值
*/