Mybatis缓存
缓存就是存储在内存中的数据,内存和硬盘的区别想必不陌生,主要是体现在读写速度价格上不是一个级别的,为了防止高并发的场景下,硬盘无法支持IO性能要求,所以就有了将数据储存在内存中的概念,也就是缓存了,使用缓存可以避免频繁与数据库交互,进而提高响应速度。
Mybatis是分为一级缓存和二级缓存的
- 一级缓存是Sqlsession级别的缓存,在操作数据库的时候需要构造SqlSession对象,在对象中会有一个HashMap用于存储缓存的数据,不同的Sqlsession是互不干扰的。
- 二级缓存是Mapeer级别的缓存,多个Sqlsession操作同一个Mapeer,也就是namespace,是可以同享二级缓存的。
一级缓存
简单应用
一级缓存是默认开启的
先看一下代码,测试一下一级缓存的应用,主要是以两次查询来测试的
@Test
public void oneCache(){
SqlSession sqlSession = this.factory.openSession();
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
List<Order> orders1 = orderMapper.selectById(1);
System.out.println(Arrays.toString(orders1.toArray()));
List<Order> orders2 = orderMapper.selectById(1);
System.out.println(Arrays.toString(orders2.toArray()));
// 一级缓存 缓存的是查询出的对象引用
System.out.println(orders1 == orders2);
}
控制台打印结果
可以看到只发生了一次向数据库查询数据,并且第一次查询和第二次查询他们的对象是相同的。这说明第一次的时候,在缓存中并没有响应的数据,所以先查询了数据库,第二次的时候因为已经存在的缓存中,所以第二次查询没有查询数据库,而是直接在缓存中拿到了数据。
再看一个刷新一级缓存的情况
@Test
public void oneCacheFlush(){
SqlSession sqlSession = this.factory.openSession();
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
List<Order> orders1 = orderMapper.selectById(1);
System.out.println(Arrays.toString(orders1.toArray()));
// 更新数据或者手动刷新都会导致一级缓存刷新
// Order order = new Order();
// order.setId(1);
// order.setTotal(new BigDecimal(50));
// orderMapper.updateOrder(order);
// sqlSession.commit();
sqlSession.clearCache();
List<Order> orders2 = orderMapper.selectById(1);
System.out.println(Arrays.toString(orders2.toArray()));
// 一级缓存 缓存的是查询出的对象引用
System.out.println(orders1 == orders2);
}
控制台
这是因为在第一次查询后,紧接着修改了数据或者手动刷新了一级缓存,导致在一级缓存中相应的缓存数据清空,所以第二次查询依然查询了数据库,增删改都会清空响应缓存数据,只要是为了防止查询时出现脏读。
流程图如下所示
原理剖析
下面带着几个问题看下面的内容,一级缓存是什么?一级缓存在什么时候被创建的?它的具体工作流程又是什么?
一级缓存是什么?
关于这个问题,其实在上面的内容中已经有了解答,它就是一个HashMap,并且它是和SqlSession相关联的HashMap,所以它的作用域只能是SqlSession范围的,其他Sqlsession是无法使用的。
那如何找到这个HashMap的具体所在位置呢?
可以看到上面的代码中,有一个手动清空一级缓存的操作,sqlSession.clearCache();
,就可以以此入手看一下,到底如何清空缓存的,那么自然也就找到了这个缓存了
如图所示,在BaseExecutor
中有一个属性是PerpetualCache
,这个就是Mybatis中内置的一级缓存对象
如图所示,这个就是这个对象如何是缓存数据以及清理数据的,可以看到最终保存数据的就是一个HashMap。
一级缓存在什么时候被创建的?
分析这个问题的时候,可以参考之前的一张图
从这张图中就可以很清晰的了解,是在第一次查询的时候进行缓存数据的,也就是在查询的时候,那么有了相应的方向,可以看一下Executor
中如何创建的。为什么是Executor,主要是在Mybatis中Executor主要就是执行Sql和数据库打交道的地方
可以在BaseExecutor
中找到这个createCacheKey
方法,就是通过这个方法去创建刚刚存储缓存的Key的,可以看一下他的Key是由几个参数组成的呢?
分别是StatementId、rowBounds、boundSql、value(param)、environmentId,一共是这五部分组成,他们分别代表
- StatementId: namespace+具体语句的Id
- rowBounds: 分页参数,假如没有设置分页参数,默认是Offset=0,Limit = Integer.MAX_VALUE
- boundSql: 具体查询Sql
- value(param): 查询时所涉及的参数
- environmentId: 这个主要是指Mybatis配置文件中的环境Id
如图,很直观了:
它的具体工作流程又是什么?
最后一个问题,他的工作流程是什么?
可以直接由createCacheKey
这个方法出发,看看他的调用方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存标识
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override