Mybatis缓存机制详解(一级缓存+二级缓存)
什么是一级缓存
MyBatis 包含了一个非常强大的 查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。mybatis 默认情况下只会开启一级缓存,也就是局部的 session 会话缓存。
首先我们要知道什么是查询缓存?查询缓存又有什么作用?
功能:mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
如下图,每一个 session 会话都会有各自的缓存,这缓存是局部的,也就是所谓的一级缓存:
什么情况下会命中一级缓存
- 相同的 sql 和 参数
- 必须是在一个会话 Session 当中
- 必须是执行 相同的方法
- 必须是相同的 namespace (同一个命名空间 -> 同一个mapper文件)
- 不能够在查询之前执行 clearCache
- 中间不能执行 任何 update ,delete ,insert (会将SqlSession中的数据全部清空)
Mybatis的缓存机制详解
一级缓存是SqlSession级别的缓存。我们都知道在操作数据库时需要构造 sqlSession对象,而在sqlSession对象中有一个数据结构(HashMap)用于存储缓存数据。
如下图:
Mybatis的缓存机制详解
从图上,我们可以看出,一级缓存区域是根据SqlSession为单位划分的。每次查询都会先从缓存区域找,如果找不到就会从数据库查询数据,然后将查询到的数据写入一级缓存中。Mybatis内部存储缓存使用的是一个HashMap对象,key为 hashCode + sqlId + sql 语句。而value值就是从查询出来映射生成的java对象。而为了保证缓存里面的数据肯定是准确数据避免脏读,每次我们进行数据修改后(增、删、改操作)就会执行commit操作,清空缓存区域。
SqlSession和Executor都是接口 ,而实现是 DefaultSqlSession 和 CacheExecutor
- Client相当于测试方法 Test1
- [email protected] 动态代理 (相当于一个伪皇帝,只是负责做转发的) == 代码中的UserMapper(代理对象) 实则没什么卵用
- Executor 接口 才是去数据库中拿数据的跑腿的
- SqlSession 是接口
- 一级缓存的实现是通过 CacheExecutor 实现的
底层实现 详解!!
在selectById方法打入断点 以及底层 实现类 PrepetualCache中的getObject(Object key)打入断点
观察 debug 的堆栈信息 可以看出
- 上图 标记绿色的框内都是 动态代理的实现 没用任何意义 只是做了传递 所以忽略这一部分
- 再往上走 我们查询的是selectOne 但是还是会去查询SelectList再去转换 通过DefaultSqlSession实现
真正与缓存相关的是这几行的实现
流程如下
这一步骤:
- 可以看出缓存key中是包含了 方法和namespace和会话 这些必须相同才会去做一个缓存命中
- 这里面封装了缓存唯一的key
- DefaultSqlSession中有一个CacheExecutor
- CacheExecutor 中有一个 Simpleexexutor
- Simpleexexutor 中有一个叫 LocalCache (PerpetualCache类型)
- LocalCache才是真正的存储缓存的地方
- LocalCache 中有一个叫cache (Hashmap <Object,Object>类型的)
一级缓存的获取 我们就打通了!!!
一级缓存的PUT流程
前几条流程与上图相同
Cache的生命周期和SqlSession相同
是否会存在数据不一致问题?
不会 因为数据库本身是具有事务隔离级别的 默认是可重复读 也就是说任何一个事务在它内部读到的数据都是一致的,所以并不存在数据不一致的问题
Mybatis二级缓存
二级缓存使用条件 必须配置namespace注解
put 二级缓存的时候 此会话必须关闭才能put (因为跨事务读 否则会出现脏读问题)
什么情况下会命中一级缓存
- 相同的 sql 和 参数
- 必须是在一个会话 Session 当中
- 必须是执行 相同的方法
- 必须是相同的 namespace (同一个命名空间 -> 同一个mapper文件)
- 不能够在查询之前执行 clearCache
- 中间不能执行 任何 update ,delete ,insert (会将SqlSession中的数据全部清空)
- 二级缓存使用条件 必须配置Cachenamespace注解
- put 二级缓存的时候 此会话必须关闭才能put (因为跨事务读 否则会出现脏读问题)
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是可以横跨跨SqlSession的。
示意图:
Mybatis的缓存机制详解
二级缓存区域是根据mapper的namespace划分的,**相同namespace的mapper查询数据放在同一个区域,**如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分,也就是根据命名空间来划分的,如果两个mapper文件的命名空间一样,那样,不同的SqlSession之间就可以共享一个mapper缓存。
示意图:
Mybatis的缓存机制详解
在默认情况下是没有开启二级缓存的,除了局部的 session 缓存。而在一级缓存中我们也介绍了,不同的SqlSession之间的一级缓存是不共享的,所以如果我们用两个SqlSession去查询同一个数据,都会往数据库发送sql。
开启二级缓存后,SqlSession之间的数据就可以通过二级缓存共享了,和一级缓存一样,当执行了insert、update、delete等操作并commit提交后就会清空二级缓存区域。当一级缓存和二级缓存同时存在时,会先访问二级缓存,再去访问各自的一级缓存,如果都没有需要的数据,才会往数据库发送sql进行查询。