前言
數據庫作爲軟件開發過程中必不可少的一門語言,一直深受廣大後端程序員的關注和喜愛,作爲移動開發來講,雖然有着更多的其他存儲方式可以使用,但是其也是並不可少的存在,特別是在某些特殊的應用場景下,因此掌握數據庫的使用在移動開發也格外重要。本次我就通過一個存儲搜索記錄的例子,來一起鞏固總結一下數據庫在Android上的使用。
功能要點
相信大家應該在不少的App上見到過搜索記錄這個功能,很簡單的一個小功能,本次博客的內容,就是用數據庫實現這個功能,那麼要實現這個功能,需要滿足那些條件呢?那肯定是針對搜索記錄的增、刪、查了
- 每次搜索時將搜索的關鍵字增加到我們的數據庫裏
- 可以刪除某一或者全部搜索記錄
- 查詢某個頁面對應的搜索記錄
對此,我設計的表含有以下三個字段,來支持搜索記錄功能的實現。
屬性 | 類型 | 含義 |
id | INTEGER(主鍵) |
id |
origin | TEXT | 來源(Activity名稱) |
word | TEXT | 關鍵詞 |
界面設計
因爲在一個App裏可能有多個頁面需要搜索功能,所以我們存儲不同頁面的搜索記錄,對此我設計了兩個頁面,主頁面和搜索頁面,主界面含有兩個入口,並根據上面三點需求設計了搜索頁。
代碼編寫
Android自帶的數據庫爲SQLite,要想在Android上使用它,我們需要通過繼承官方的SQLiteOpenHelper來實現數據庫工具類。
創建ShDbHelper類繼承SQLiteOpenHelper並重寫onCreate()和onUpgrade()方法,這裏我聲明瞭一些常量方便後面使用。
const val DB_VERSION = 1
const val DB_NAME = "my.db"
const val TABLE_NAME = "searchHistory"
const val ID = BaseColumns._ID
const val WORD = "word"
const val ORIGIN = "origin"
class ShDbHelper : SQLiteOpenHelper {
constructor(context: Context) : super(context, DB_NAME, null, DB_VERSION)
override fun onCreate(db: SQLiteDatabase) {
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
建表
我們要存儲記錄就要得先有個表,在onCreate()方法裏執行sql語句創建。Android提供了兩類方法供我們執行sql操作。第一類我稱爲全sql方法,第二類爲半sql方法。全sql方法的和半sql方法的區別就在sql語句上,前者需要編寫整串sql語句,對sql語句的熟悉程度要求較高,主要方法有execSQL()、rawQuery()。後者是Android將sql拆分,封裝成了一些參數,只需編寫部分sql語句。如果對sql語句非常熟悉,可以使用前者,如果怕拼寫錯誤,確保安全的可以使用後者。
- execSQL():用於執行insert、delete、update和CREATE TABLE之類有更改行爲的SQL語句。
- rawQuery():用於執行select語句。
本例大部分使用後者進行編寫。
//創建表sql語句
val create_sql = "CREATE TABLE $TABLE_NAME ($ID INTEGER PRIMARY KEY AUTOINCREMENT,$ORIGIN TEXT NOT NULL,$WORD TEXT NOT NULL)"
//執行一個不是SELECT的SQL語句或任何其他返回數據的SQL語句。它無法返回任何數據
db.execSQL(create_sql)
插入
存儲搜索記錄就要往表裏執行插入操作,通過一個可寫的SQLiteDatabase對象執行insert插入方法,該方法有三個參數,第一個參數爲要插入的表的名稱,第二個傳null就行,第三個參數是一個ContentValues對象,官方提供了ContentValues對象供我們將需要插入的數據封裝起來,就像使用map一樣。
/*
* 插入搜索記錄
* */
fun insert(origin: String, word: String): Long {
val value = ContentValues()
value.put(ORIGIN, origin)
value.put(WORD, word)
return writableDatabase.insert(TABLE_NAME, null, value)
}
查詢
SQLiteDatabase提供了query()方法供我們對數據庫進行查詢操作,具體的參數含義看如下代碼註釋。
/*
* 查詢某頁的所有搜索記錄
* */
fun query(origin: String): ArrayList<String> {
//需要查詢的參數
val columns = arrayOf(WORD)
//查詢條件
val selection = "$ORIGIN =?"
//查詢條件對應的值
val selectionArgs = arrayOf(origin)
//按ID倒序排列
val orderBy = "$ID DESC"
val cursor = readableDatabase.query(TABLE_NAME, columns, selection, selectionArgs, null, null, orderBy)
if (cursor.count != 0) {
val list = arrayListOf<String>()
while (cursor.moveToNext()) {
list.add(cursor.getString(0))
}
cursor.close()
return list
}
return arrayListOf()
}
如果使用rawQuery(),那麼上面這些參數對應的sql語句爲
“SELECT word FROM searchHistory WHERE origin =? ORDER BY _id DESC”
這兩個方法中sql語句的?代表佔位符,具體的值爲後面傳遞的參數。這個值其實也可以直接寫進sql,後面那個參數傳null。
爲了方便使用,我將查詢出來的數據封裝到了一個列表返回。
刪除
SQLiteDatabase提供了delete()方法供我們對數據庫進行刪除操作。參數和查詢類似,含義看如下注釋。
/*
* 刪除某頁某條搜索記錄
* */
fun delete(origin: String, word: String): Int {
//表名稱、刪除條件、條件參數
return writableDatabase.delete(TABLE_NAME, "$ORIGIN =? and $WORD =?", arrayOf(origin, word))
}
當然,有時我們需要刪除某個頁面所有的記錄,對此只需去掉word條件就行。
/*
* 刪除某頁所有搜索記錄
* */
fun deleteAll(origin: String): Int {
return writableDatabase.delete(TABLE_NAME, "$ORIGIN =?", arrayOf(origin))
}
到這我們本次例子所需要的基本功能增、刪、查就都有了,不過從用戶的使用角度來看本次例子的功能並沒有完善,還缺少用戶體驗,比如
- 搜索同一關鍵詞多次時關鍵詞也被多次存儲記錄
- 若記錄存在相同關鍵詞,當前關鍵詞位置應當顯示在記錄最前面
- 搜索記錄條數未設限制
- 最舊的記錄應該刪掉
思考一番能發現,其實上面這四個問題都是應該在進行插入操作時做處理的。第一二個問題,都是一種解決辦法,那就是在將關鍵詞插入數據庫的時候,先查詢數據庫有沒有存儲同樣的關鍵詞,有就刪除再插入。第三四個也可以看做一個問題,對此需要額外設置一個記錄最大條數的值,並且在當插入新的關鍵詞時,如果當前記錄數已超過最大值,則刪掉最舊的一條記錄,在執行插入操作。
針對第一二個問題,刪除單個關鍵詞的方法已經有了,還缺一個查詢單個關鍵詞的方法,再已有查詢方法的sql語句上增加一個條件即可。
/*
* 查詢某頁某條搜索記錄
* */
private fun query(origin: String, word: String): Int {
val projection = arrayOf(WORD)
val selection = "$ORIGIN =? and $WORD =?"
val selectionArgs = arrayOf(origin, word)
val cursor = readableDatabase.query(TABLE_NAME, projection, selection, selectionArgs, null, null, null)
return cursor.count
}
對第三四個問題,出了設置最大值,也是先查後刪,只不過查詢條件變爲查詢最舊的一條記錄的ID,然後在根據此ID刪掉對應數據。
private fun queryOldest(origin: String): String {
val projection = arrayOf(ID)
val selection = "$ORIGIN =?"
val selectionArgs = arrayOf(origin)
val orderBy = "$ID ASC"
val cursor = readableDatabase.query(TABLE_NAME, projection, selection, selectionArgs, null, null, orderBy)
cursor.moveToFirst()
val id = cursor.getString(0)
cursor.close()
return id
}
private fun deleteOldest(origin: String) {
val id = queryOldest(origin)
val i = writableDatabase.delete(TABLE_NAME, "$ID =?", arrayOf(id))
if (i > 0)
Log.i("kkk", "已刪除最舊的記錄")
}
還有一種簡單的寫法,將查詢刪除總結到一句sql中。
private fun deleteOldest(origin: String) {
val i = writableDatabase.delete(TABLE_NAME, "$ID =(select min($ID) from $TABLE_NAME where $ORIGIN =?)", arrayOf(origin))
if (i > 0)
Log.i("kkk", "已刪除最舊的記錄")
}
用全sql寫的話就是
DELETE FROM $TABLE_NAME WHERE $ID = (SELECT MIN($ID) FROM $TABLE_NAME WHERE $ORIGIN = $origin)
然後,插入的方法變成這樣
/*
* 插入搜索記錄
* */
fun insert(origin: String, word: String): Long {
//如若已有記錄,刪除並從新插入
if (query(origin, word) > 0)
delete(origin, word)
//新增超過每頁記錄最大存儲數時刪除最舊當頁最舊數據
val size = query(origin).size
if (size >= MAX_COUNT)
deleteOldest(origin)
val value = ContentValues()
value.put(ORIGIN, origin)
value.put(WORD, word)
return writableDatabase.insert(TABLE_NAME, null, value)
}
剩下的跟界面相關的代碼我就不貼了,都傳到github上去了,有興趣的小夥伴可以克隆下來看看。戳我
本人不才,例子就寫到這裏了,都是一些比較基礎的東西,數據庫的東西還有得研究,寫的有什麼不正確的地方還希望大家能指出來,與大家共勉~