Mybatis的一级缓存

62 阅读5分钟

1. 首先来执行一下,看看什么时候会触发一级缓存

2.1 相同的sql与参数

SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
T00NameDO name2 = mapper.selectById(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
true

2.2 相同的sql与不同的参数

SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
T00NameDO name2 = mapper.selectById(2L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 2(Long)
<==      Total: 0
false

2.3 相同的sql与参数,不同的statementId

SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
T00NameDO name2 = mapper.selectById1(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
false

2.4 相同的sql与参数,statementId,不同的会话

SqlSession sqlSession1 = factory.openSession(true);
T00NameDO name1 = sqlSession1.getMapper(NameMapper.class).selectById(1L);
SqlSession sqlSession2 = factory.openSession(true);
T00NameDO name2 = sqlSession2.getMapper(NameMapper.class).selectById(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
false

2.5 相同的sql与参数,statementId,相同会话,不同的分页参数

SqlSession sqlSession = factory.openSession(true);
List<T00NameDO> nameList1 = sqlSession.selectList("com.lr.mybatis.NameMapper.selectById", 1L, new RowBounds(0, 1));
List<T00NameDO> nameList2 = sqlSession.selectList("com.lr.mybatis.NameMapper.selectById", 1L, new RowBounds(0, 2));
System.out.println(nameList1.get(0) == nameList2.get(0));

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
false

2.6 相同的sql与参数,statementId,相同会话,分页参数,sqlsession清空缓存

SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
sqlSession.clearCache();
T00NameDO name2 = mapper.selectById(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
false

2.7 相同的sql与参数,statementId,相同会话,分页参数,sqlsession不清空缓存,添加注解@Options(flushCache = Options.FlushCachePolicy.TRUE)

@Select({" select * from t00_name where id=#{1}"})
@Options(flushCache = Options.FlushCachePolicy.TRUE)
T00NameDO selectById(Long id);

SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
T00NameDO name2 = mapper.selectById(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
false

2.8 相同的sql与参数,statementId,相同会话,分页参数,sqlsession不清空缓存,不添加注解@Options(flushCache = Options.FlushCachePolicy.TRUE),查询中间插入修改

@Select({" select * from t00_name where id=#{1}"})
T00NameDO selectById(Long id);
@Update("update t00_name set name = #{name} where id = #{id}")
int setName(@Param("id") Long id, @Param("name") String name);


SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
mapper.setName(1L, "lr");//哪怕修改的不是改行的数据仍旧不会命中缓存
T00NameDO name2 = mapper.selectById(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, test
<==      Total: 1
==>  Preparing: update t00_name set name = ? where id = ?
==> Parameters: lr(String), 1(Long)
<==    Updates: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, lr
<==      Total: 1
false

2.9 相同的sql与参数,statementId,相同会话,分页参数,sqlsession不清空缓存,不添加注解@Options(flushCache = Options.FlushCachePolicy.TRUE),中间不插入修改,配置缓存作用域LocalCacheScope.STATEMENT

configuration.setLocalCacheScope(LocalCacheScope.STATEMENT);

SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
T00NameDO name2 = mapper.selectById(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, lr
<==      Total: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, lr
<==      Total: 1
false

2.10 相同的sql与参数,statementId,相同会话,分页参数,sqlsession不清空缓存,不添加注解@Options(flushCache = Options.FlushCachePolicy.TRUE),中间不插入修改,不配置缓存作用域LocalCacheScope.STATEMENT,sqlSession进行commit/rollback操作

SqlSession sqlSession = factory.openSession(true);
NameMapper mapper = sqlSession.getMapper(NameMapper.class);
T00NameDO name1 = mapper.selectById(1L);
sqlSession.commit();
sqlSession.rollback();
T00NameDO name2 = mapper.selectById(1L);
System.out.println(name1 == name2);

==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, lr
<==      Total: 1
==>  Preparing: select * from t00_name where id=?
==> Parameters: 1(Long)
<==    Columns: id, name
<==        Row: 1, lr
<==      Total: 1
false

2. 基本结构

从上面的测试我们可以看出,一级缓存命中的条件还是比较多的,可以做一下归类

  1. sqlsession
  2. statementId
  3. sql
  4. sql的参数
  5. 分页参数
  6. flushCache
  7. LocalCacheScope
  8. commit/rollback
  9. 中间有update操作
  10. resultHandle

那么显而易见的mybatis的一级缓存是针对于Sesision级别的,每次于数据库的会话都会创建出一个sqlsession,执行器executor会在每个sqlsession中被new出来,一级缓存我们都知道存在于基础执行器BaseExecutor中, image.png 所以一级缓存与sqlsession的关系就是一对一

一级缓存在执行器中的结构为PerpetualCache,这个类对象中维护了一个HashMap image.png 接下来我们知道缓存实际上是个HashMap,那么它的Key是什么呢? 我们从基础执行器BaseExecuse的query方法入手

image.png 一共有两个query方法,可以从代码中看出,最终调用的是下面的query方法,而上面的方法获取了一级缓存的Key,我们先从CacheKey的结构入手

image.png CacheKey的update方法 image.png CacheKey的equals方法 image.png

image.png 那么接下来看看CacheKey是如何进行创建的 image.png 我们可以看到一共update了6次,msId+offset+limit+sql+parameterValue+environmentId,刚好符合我们上述2~5的情况,那么剩下的情况我们在分析一下,从实际执行的query方法可以看到这么一行, 当我们加上这个注解的时候,那么会清空一级缓存,所以无法命中6

if (queryStack == 0 && ms.isFlushCacheRequired()) {
  clearLocalCache();
}

7的情况就根据下面的代码分析

if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  // issue #482
  clearLocalCache();
}

8的情况为以下代码,执行的时候我们也可以看到清空本地缓存 image.png 9的情况需要去参考update方法 image.png 10的情况可以从主流程中分析,如果ResultHandler为空,才会命中缓存。这个留着后面再做学习