一级缓存

HeJin大约 4 分钟源码解析Mybatis源码解析

缓存体系

image-20221225105221758
image-20221225105221758

一级缓存命中场景

缓存一般是key-value的。所以一级缓存key的设计,就会影响到缓存的命中。

image-20221225105518982
image-20221225105518982

一级缓存源码分析

一级缓存命中

@Test
public void testFirstCache() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    User user = userMapper.selectById(2);
    User user2 = userMapper.selectById(2);
    System.out.println(user == user2);
}

同一个会话(SqlSession没有关闭),相同的查询sql,相同的查询参数,相同的statement(getMapper底层还是调用的SqlSession的方法),返回行是默认的也相同。

image-20221225112901812
image-20221225112901812

1.进入MapperProxy动态代理逻辑。

image-20221225113114969
image-20221225113114969

2.进入MapperMethod,根据不同的执行类型调用对应的SqlSession方法执行逻辑。

image-20221225113623955
image-20221225113623955

3.进入DefaultSqlSession,这是SqlSession的实现类。最终会进入该类的selectList()方法。

image-20221225114025545
image-20221225114025545

4.进入CachingExecutor。这个执行器是处理二级缓存相关的逻辑,因为我们第一次查询没有提交,所以二级缓存不会生效。如果你没有开启二级缓存,也不会生效。我们现在主要研究一级缓存,先不关注二级缓存。

首先会进行二级缓存的判断查询,这里我开启了mybatis的二级缓存,所以进入了二级缓存逻辑里面。如果没有开启二级缓存,会直接交给下一个执行器执行。

image-20221225115038607
image-20221225115038607

5.进入BaseExecutor。这个是执行器的公共抽象类,定义了不同执行器之间的共性。比如一级缓存就是在这里定义的。因为mybatis的一级缓存是默认打开的。也无法关闭,只能配置每次查询刷新缓存达到相同的目的。所以,每次在查数据库之前,都会先查询本地缓存(一级缓存)。因此,把这个逻辑抽离了出来。

image-20221225120025266
image-20221225120025266

6.进入PerpetualCache。这个是处理一级缓存的类。类名翻译过来是永久缓存。也就是说,一级缓存是一直存在的。除非进行修改操作或者关闭程序。

image-20221225121142380 查看BaseExecutor中的update()方法,发现的确有清空本地缓存的操作:

image-20221225134818197
image-20221225134818197

也就是说,只要在同一个会话中,会话没有关闭之前。只要有修改操作,这个会话中的一级缓存就会被清空。这和我们之前了解到的一级缓存命中情况是相符的。

7.BaseExecutor中,因为现在我们的查询是符合一级缓存的条件的,所以会查询到数据:

image-20221225121716581
image-20221225121716581

对于如何从数据库查询,可以查看Mybatis执行器中的SqlSession查询流程。

从样就查询到了数据,而且是命中一级缓存的情况。接下来就会把数据按调用链路进行返回。最后我们就从一级缓存中查询到了数据。

image-20221225121716581
image-20221225121716581

不走一级缓存

如果不满足一级缓存的获取条件,或者是第一次查询,都是会直接从数据库中查询的,查询到数据之后会填充到一级缓存。代码在上面的第7步,BaseExecutor中的queryFromDatabase()方法:

image-20221225130908654
image-20221225130908654

BaseExecutor

// doQuery()方法是BaseExecutor定义的抽象方法,负责查询数据库,BaseExecutor只是定义,实现交给具体的子类
// 因此查询数据库是由BaseExecutor的子类实现的,常用的有SimpleExecutor(默认)、ReuseExecutor(可重用执行器:预编译)、BatchExecutor(批量处理执行器)
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  // 数据库中查询的数据list,会放入一级缓存(HashMap)  
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

具体执行器中的doQuery()方法实现,可以查看Mybatis执行器中的SqlSession查询流程分析。

一级缓存执行流程

image-20221225132049876
image-20221225132049876