前言
在上节我们介绍了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 = 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 = 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 = 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, 今天关于数据库与缓存联合使用的知识就讲到这里了,谢谢大家。