背景
项目上使用了tk.mybatis查询数据,多个方法中使用了selectByPrimaryKey方法。由于MyBatis默认开启了缓存,所以当在调用第一个方法获取对象后,如果后续对这个获取的对象进行了赋值操作,当再次调用selectByPrimaryKey方法时,获取到的就是上一次操作过后的对象,而并不是数据库的原始数据,如果业务需要用到原始数据时就会有问题。
DwModelTable table = dwModelTableMapper.selectByPrimaryKey(id);
// 操作对象,比如
table.setModelName(table.getModelName().toUpperCase() + "123");
以上方法如果查询的modelName=abc,赋值后变为了modelName=abc123。那在下次再调用dwModelTableMapper.selectByPrimaryKey(id)时,就不会去调用数据库查询,而是直接返回修改后的对象,modelName=abc123。
解决方案
本文只为记录在不关闭缓存的情况下解决这个问题,网上一大堆说关闭缓存就能解决的文章,没考虑到如果业务需要在非首次查询时用到首次查询后操作的对象的场景。
DwModelTable copyTable = dwModelTableMapper.selectByPrimaryKey(id);
DwModelTable table = new DwModelTable();
BeanUtils.copyProperties(copyTable, table);
如果需要每次都查询数据库获取,就需要创建临时对象,将查询出的数据库对象属性值拷贝到临时对象后,后面的业务去操作临时对象,这样就不会被缓存所影响了。
引申解读
MyBatis可以非常方便地定制和配置缓存,极大地提升查询效率。MyBatis默认定义了一级缓存和二级缓存,默认开启一级缓存,二级缓存需要手动配置开启。为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
1. 一级缓存
一级缓存为SqlSession级别的缓存,也称为本地缓存: 与数据库同一次会话期间查询到的数据会放在本地缓存中。 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库
- 查询不同东西会刷新缓存
- insert、update、delete语句会刷新缓存,比如查询1号用户,然后更新2号用户,这时候再查询1号用户,则会重新查询
- 查询不同的Mapper.xm会刷新缓存
- 一级缓存默认开启,只在一次sqlsession中有效,也就是拿到连接的一个过程中有效
- 一级缓存就是一个Map
2. 二级缓存
二级缓存为namespace级别的缓存,也叫全局缓存,一个名称空间,对应一个二级缓存。一级缓存作用域太低,所有诞生了二级缓存。二级缓存被同一个 namespace 下的多个 SqlSession 共享,是一个全局的变量。MyBatis 的二级缓存不适应用于映射文件中存在多表查询的情况。
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话被查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
如何开启二级缓存请自行搜索