Mybatis源码解析(十):一级缓存和二级缓存

Mybatis源码系列文章

手写源码(了解源码整体流程及重要组件)

Mybatis源码解析(一):环境搭建

Mybatis源码解析(二):全局配置文件的解析

Mybatis源码解析(三):映射配置文件的解析

Mybatis源码解析(四):sql语句及#{}、${}的解析

Mybatis源码解析(五):SqlSession会话的创建

Mybatis源码解析(六):缓存执行器操作流程

Mybatis源码解析(七):查询数据库主流程

Mybatis源码解析(八):Mapper代理原理

Mybatis源码解析(九):插件机制

Mybatis源码解析(十):一级缓存和二级缓存


目录

  • 前言
  • 一、缓存策略
  • 二、一级缓存演示
  • 三、一级缓存源码
    • 1、作用范围
    • 2、缓存失效
  • 四、二级缓存演示
  • 五、二级缓存源码
    • 1、\标签的解析
    • 2、Cache对象存在位置
    • 3、二级缓存的存和取
      • 1)tcm.putObject:二级缓存中存值
      • 2) tcm.getObject:二级缓存中取值
      • 3)commit操作
    • 4、缓存失效
  • 总结


前言

  • Mybatis源码解析(六):缓存执行器操作流程-此篇讲了一级缓存和二级缓存的数据类型HashMap,key的组成部分以及一二级缓存与查询数据库的顺序
  • 本篇讲解下一级缓存和二级缓存的作用范围、执行流程底层实现原理

一、缓存策略

  • 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的
  • 二级缓存是mapper级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的

Mybatis源码解析(十):一级缓存和二级缓存_第1张图片

二、一级缓存演示

  • 一级缓存默认开启

演示一

  • 同一个sqlSession的相同查询,sql日志打印一次而且两次对象相等,第二次查询结果是第一次查询的缓存结果

Mybatis源码解析(十):一级缓存和二级缓存_第2张图片

演示二

  • 同一个sqlSession的两次相同查询,中间添加一个更新操作,每次查询都要打印日志而且结果不相等,第一次查询结果缓存失效了
  • 更新操作,即使没有commit,结果不变,那么失效原因就在update里面

Mybatis源码解析(十):一级缓存和二级缓存_第3张图片

演示三

  • 不同的sqlSession的相同查询,两次sql日志打印而且对象不相等,第二次查询没有拿到第一次查询结果的缓存

Mybatis源码解析(十):一级缓存和二级缓存_第4张图片

三、一级缓存源码

1、作用范围

  • 查询数据库方法,在此之前已经判断一级缓存中不存在查询数据
  • key(Cache对象)的组成部分:statementId、分页参数、带?的sql、参数值、环境id
  • 一级缓存就是PerpetualCache localCache
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // 1. 首先向本地缓存中存入一个ExecutionPlaceholder的枚举类占位value
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // 2. 执行doQuery方法
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // 3. 执行完成移除这个key
    localCache.removeObject(key);
  }
  // 4. 查询结果存入缓存中
  localCache.putObject(key, list);
  // 5. 如果MappedStatement的类型为CALLABLE,则向localOutputParameterCache缓存中存入value为parameter的缓存
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

查看一级缓存对象PerpetualCache localCache

  • 说到底一级缓存就是cache这个HashMap对象
public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }
  ...
}

查看一级缓存对象PerpetualCache的位置

  • 一级缓存对象在执行器基础抽象类下
  • Mybatis源码解析(五):SqlSession会话的创建-此篇讲了SqlSessioin的创建包含了不同执行器的创建,而BaseExecutor又是不同执行器的基类,必定需要创建
  • 所以,一级缓存是跟随着SqlSession的
  • 一级缓存演示一:相同的sqlSession就可以从localCache中获取以前存的value
  • 一级缓存演示三:不相同的sqlSession,sqlSession1的缓存中有值,但sqlSession2需要到它的缓存中获取,所以拿不到

Mybatis源码解析(十):一级缓存和二级缓存_第5张图片

2、缓存失效

  • 根据一级缓存的演示二可知,相同的sqlSession的update方法会导致缓存失效

进入udpate源码

  • 这里有个清除缓存方法,将一级缓存localCache的Map对象clear
  • 一级缓存的演示二:缓存map已被清空,所以缓存失效
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
}
@Override
public void clearLocalCache() {
  if (!closed) {
    localCache.clear();
    localOutputParameterCache.clear();
  }
}

四、二级缓存演示

  • 二级缓存不是默认开启
  • Mapper配置文件中配置标签
    Mybatis源码解析(十):一级缓存和二级缓存_第6张图片
  • 实体类要实现Serializable接口,因为二级缓存会将对象写进硬盘,就必须序列化,以及兼容对象在网络中的传输

演示一

  • 不同的sqlSession相同的查询,sql日志只有一个,说明二级缓存生效了
  • 对象不相等。内容一样,地址不一样,后面源码说
  • 两次查询,只有调用commit或者close方法,二级缓存才会生效
  • 记录了命中率,第一次缓存中没有,命中率0.0,第二次查询缓存中有,查询两次,缓存命中率0.5

Mybatis源码解析(十):一级缓存和二级缓存_第7张图片

演示二

  • 两次查询之间添加更新操作,依然缓存命中0.5
  • 二级缓存,只有更新操作commit以后才会缓存失效
  • 一级缓存,只需要更新操作,不需要commit就会缓存失效

Mybatis源码解析(十):一级缓存和二级缓存_第8张图片

五、二级缓存源码

1、标签的解析

捋一下xml与对象之间的关系

  • 全局配置文件会被解析成Configuration对象
  • 映射配置文件会被解析成多个MappedStatement对象,每个