前言
在上節我們介紹了Room
數據庫的使用,大家感興趣的話,可以參考以下文章:
kotlin版Room數據庫 — 基本使用
那麼在數據庫的使用過程中,我們經常想有一些加快效率的方法。二級緩存
的概念便是這麼來的: 先緩存中找數據,緩存中能找到的話就直接使用,緩存中找不到的話就取數據庫中找
。大致就是這麼個流程。但是我們在用Room
數據庫時,是有結合自己封裝的一個緩存幫助類Cache
來使用的,然後在業務流程中出現了取值不正確的問題,下面就來分析下這個小問題吧。
今天涉及到的內容有:
- 出現的問題
- 節點介紹
- 緩存原理介紹
- 排查問題
- 修正邏輯
一. 出現的問題
今天我在數據操作上大致是這麼個流程:
存數據 ---> 修改數據 ---> 取數據 ---> 其他業務邏輯
問題就出在取數據
環節,取的不是我存的數據,然後就導致我後面的其他業務邏輯
無法展開。那麼,這裏肯定是存數據
,修改數據
或者取數據
的某個環節中出現了問題。下面就來具體看看這幾個節點。
二. 節點介紹
2.1 存數據
下面給出存數據
環節的示例代碼:
var userDaoHelper: UserDaoHelper = UserDaoHelper()
LogUtil.i("=======存入初始數據========")
var user: User = User()
user.name = "張三"
user.age = 12
user.sex = "男"
AppDataBase.instance().userDao().insert(user)
//遍歷結果
var list: MutableList<User> = userDaoHelper.getAll()
list.forEach {
LogUtil.i("=====遍歷所有數據===name=${it.toString()}")
}
這裏是使用Room
數據庫的insert
方法進行存儲的。以上代碼寫了數據存儲和遍歷查詢,代碼很簡單,沒什麼問題,那麼接着讓我們去看下一個節點。
2.2 修改數據
修改數據的示例代碼如下:
LogUtil.i("=======查詢叫張三的對象========")
var tempUser:User?=getUserByName("張三")
if(tempUser==null){
LogUtil.i("========user=null")
}else{
LogUtil.i("========user=${user.toString()}")
}
LogUtil.i("=======更新操作========")
//更新
tempUser!!.hobby="大神"
userDaoHelper.update(tempUser!!)
LogUtil.i("========更新tempUser=${tempUser!!.toString()}")
這裏先是調用getUserByName("張三")
獲取存儲的User
對象,然後更新完tempUser
後通過userDaoHelper.update(tempUser!!)
方法存到Room
數據中。這裏,我們繼續往下看,後面再細看getUserByName("張三")
方法。
2.3 查詢環節
最後一步是查詢已存儲的數據,示例代碼如下:
LogUtil.i("=======更新後查詢結果========")
//再取值
var lastUser:User?=getUserByName("張三")
if(lastUser==null){
LogUtil.i("========user=null")
}else{
LogUtil.i("========user=${lastUser.toString()}")
}
LogUtil.i("=======更新後查數據庫結果========")
var listp: MutableList<User> = userDaoHelper.getAll()
listp.forEach {
if("張三".equals(it.name)){
LogUtil.i("========user=${it.toString()}")
}
}
這個地方用了兩種查詢方式,一種是通過getUserByName("張三")
方法查詢出User
,另一種是通過Room
數據庫相關方法封裝出的方法userDaoHelper.getAll()
來獲取數據。
執行完畢後,我們可以來看看log:
=======存入初始數據========
=====遍歷所有數據===name=User(id=1, name=張三, age=12, hobby=null, sex=null)
=======查詢叫張三的對象========
========取數據庫======
========user=User(id=0, name=張三, age=12, hobby=null, sex=男)
=======更新操作========
========更新tempUser=User(id=1, name=張三, age=12, hobby=大神, sex=null)
=======更新後查詢結果========
========取cache======
========user=User(id=1, name=張三, age=12, hobby=null, sex=null)
=======更新後查數據庫結果========
========user=User(id=1, name=張三, age=12, hobby=大神, sex=null)
以上log打印表示的執行邏輯大概是(主要看User
對象中的hobby
屬性):
存入user
時,hobby=null
然後通過getUserByName("張三")
取出user
時,hobby=null
然後更新user
時,hobby=大神
最後getUserByName("張三")
查詢出的user
中,hobby=null
而數據庫查詢出user
時, hobby=大神
這裏可以看出存更新後的user
是成功的,因爲數據庫查出來user
的 hobby=大神
,那麼問題就出在getUserByName("張三")
方法上了。
三. 緩存原理介紹
3.1 getUserByName(name:String) 方法
下面就來看看getUserByName("張三")
方法示例代碼:
private fun getUserByName(name:String): User? {
var user:User?=null
var userDaoHelper: UserDaoHelper = UserDaoHelper()
var obj: Any? = Cache.getInstance().getObject(name, User::class.java)
if (obj != null) {
LogUtil.i("========取cache======")
user= obj as User
}else{
//遍歷結果
var list: MutableList<User> = userDaoHelper.getAll()
var index:Int=0
while(true){
var temp:User=list[index]
if(name.equals(temp.name)){
user=temp
LogUtil.i("========取數據庫======")
//存儲對象
Cache.getInstance().putObject(name, user)
break
}
index++
}
}
return user
}
ok,可以看到取數據的邏輯是:先從緩存中取數據,若緩存中沒有,則從數據庫取,並在數據庫查出數據的同時,將數據存到緩存中
接着讓我們來了解下Cache
緩存邏輯。
3.2 Cache原理
Cache
是先在內存中取數據,存儲利用Lru
算法,如果內存中沒有,則在手機硬盤中獲取。而手機硬盤採用的是文件方式存儲,即硬存儲。
四.排查問題
然後再看整個邏輯:
先存數據 ---> 更新Room
數據庫數據 ---> 緩存中找數據,沒有則去Room
數據庫查,查到的同時將數據設置到緩存中,若緩存中有則直接返回數據。
於是這裏出現了一個問題:
存時ok,通過緩存查是錯誤數據,但是通過數據庫查卻是正確數據。
那麼問題便出在緩存找數據上了。Cache
的強悍存儲之處在於,它最終採用的存儲方式是文件存儲
來保持數據的持久性。那麼如果上一次數據存儲到內存之後,若之後進行了其他數據處理,卻沒去更新緩存,則緩存中取到的可能還是上一次的數據,而不是最新數據。
五.修正邏輯
經過以上分析,當我們在使用數據庫時有結合緩存使用的話,我們在操作數據庫的增,刪,改時,記得要去更新緩存,然後在取值時要用以下邏輯
1. 若緩存中有,直接返回
2. 若緩存中無,去查數據庫
3. 若數據庫中有,返回數據的同時,將數據存到緩存中,方便下次使用時取出
4. 若數據庫中無,則直接返回空
ok, 今天關於數據庫與緩存聯合使用的知識就講到這裏了,謝謝大家。