Mybatis缓存机制详解(一级缓存+二级缓存)

什么是一级缓存

MyBatis 包含了一个非常强大的 查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。mybatis 默认情况下只会开启一级缓存,也就是局部的 session 会话缓存

首先我们要知道什么是查询缓存?查询缓存又有什么作用?

功能:mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。

如下图,每一个 session 会话都会有各自的缓存,这缓存是局部的,也就是所谓的一级缓存
Mybatis缓存机制详解(一级缓存+二级缓存)


什么情况下会命中一级缓存

  1. 相同的 sql参数
  2. 必须是在一个会话 Session 当中
  3. 必须是执行 相同的方法
  4. 必须是相同的 namespace (同一个命名空间 -> 同一个mapper文件)
  5. 不能够在查询之前执行 clearCache
  6. 中间不能执行 任何 update ,delete ,insert (会将SqlSession中的数据全部清空)

Mybatis的缓存机制详解

一级缓存是SqlSession级别的缓存。我们都知道在操作数据库时需要构造 sqlSession对象,而在sqlSession对象中有一个数据结构(HashMap)用于存储缓存数据。

如下图:
Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis的缓存机制详解

从图上,我们可以看出,一级缓存区域是根据SqlSession为单位划分的。每次查询都会先从缓存区域找,如果找不到就会从数据库查询数据,然后将查询到的数据写入一级缓存中。Mybatis内部存储缓存使用的是一个HashMap对象,key为 hashCode + sqlId + sql 语句。而value值就是从查询出来映射生成的java对象。而为了保证缓存里面的数据肯定是准确数据避免脏读,每次我们进行数据修改后(增、删、改操作)就会执行commit操作,清空缓存区域。


Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis缓存机制详解(一级缓存+二级缓存)

SqlSession和Executor都是接口 ,而实现是 DefaultSqlSession 和 CacheExecutor

  • Client相当于测试方法 Test1
  • [email protected] 动态代理 (相当于一个伪皇帝,只是负责做转发的) == 代码中的UserMapper(代理对象) 实则没什么卵用
  • Executor 接口 才是去数据库中拿数据的跑腿的
  • SqlSession 是接口
  • 一级缓存的实现是通过 CacheExecutor 实现的

底层实现 详解!!

Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis缓存机制详解(一级缓存+二级缓存)

在selectById方法打入断点 以及底层 实现类 PrepetualCache中的getObject(Object key)打入断点
观察 debug 的堆栈信息 可以看出

  1. 上图 标记绿色的框内都是 动态代理的实现 没用任何意义 只是做了传递 所以忽略这一部分
  2. 再往上走 我们查询的是selectOne 但是还是会去查询SelectList再去转换 通过DefaultSqlSession实现

Mybatis缓存机制详解(一级缓存+二级缓存)

真正与缓存相关的是这几行的实现

流程如下
Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis缓存机制详解(一级缓存+二级缓存)

这一步骤:

  • 可以看出缓存key中是包含了 方法和namespace和会话 这些必须相同才会去做一个缓存命中
  • 这里面封装了缓存唯一的key

Mybatis缓存机制详解(一级缓存+二级缓存)

  • DefaultSqlSession中有一个CacheExecutor
  • CacheExecutor 中有一个 Simpleexexutor
  • Simpleexexutor 中有一个叫 LocalCache (PerpetualCache类型)
  • LocalCache才是真正的存储缓存的地方
  • LocalCache 中有一个叫cache (Hashmap <Object,Object>类型的)

Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis缓存机制详解(一级缓存+二级缓存)

Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis缓存机制详解(一级缓存+二级缓存)

一级缓存的获取 我们就打通了!!!

Mybatis缓存机制详解(一级缓存+二级缓存)

一级缓存的PUT流程

前几条流程与上图相同
Mybatis缓存机制详解(一级缓存+二级缓存)
Cache的生命周期和SqlSession相同

是否会存在数据不一致问题?

不会 因为数据库本身是具有事务隔离级别的 默认是可重复读 也就是说任何一个事务在它内部读到的数据都是一致的,所以并不存在数据不一致的问题


Mybatis二级缓存

Mybatis缓存机制详解(一级缓存+二级缓存)

二级缓存使用条件 必须配置namespace注解
put 二级缓存的时候 此会话必须关闭才能put (因为跨事务读 否则会出现脏读问题)

什么情况下会命中一级缓存

  1. 相同的 sql参数
  2. 必须是在一个会话 Session 当中
  3. 必须是执行 相同的方法
  4. 必须是相同的 namespace (同一个命名空间 -> 同一个mapper文件)
  5. 不能够在查询之前执行 clearCache
  6. 中间不能执行 任何 update ,delete ,insert (会将SqlSession中的数据全部清空)
  7. 二级缓存使用条件 必须配置Cachenamespace注解
  8. put 二级缓存的时候 此会话必须关闭才能put (因为跨事务读 否则会出现脏读问题)

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是可以横跨跨SqlSession的。

示意图:
Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis的缓存机制详解

二级缓存区域是根据mapper的namespace划分的,**相同namespace的mapper查询数据放在同一个区域,**如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分,也就是根据命名空间来划分的,如果两个mapper文件的命名空间一样,那样,不同的SqlSession之间就可以共享一个mapper缓存。

示意图:
Mybatis缓存机制详解(一级缓存+二级缓存)
Mybatis的缓存机制详解

在默认情况下是没有开启二级缓存的,除了局部的 session 缓存。而在一级缓存中我们也介绍了,不同的SqlSession之间的一级缓存是不共享的,所以如果我们用两个SqlSession去查询同一个数据,都会往数据库发送sql。

开启二级缓存后,SqlSession之间的数据就可以通过二级缓存共享了,和一级缓存一样,当执行了insert、update、delete等操作并commit提交后就会清空二级缓存区域。当一级缓存和二级缓存同时存在时,会先访问二级缓存,再去访问各自的一级缓存,如果都没有需要的数据,才会往数据库发送sql进行查询。