MyBatis一级、二级缓存

MyBatis缓存分析



一、概述

  • 基本概念:为了减轻数据库的查询压力,减少与底层数据库的交互次数,提高数据查询的效率,提供的缓存机制。
  • MyBatis默认定义了两级缓存,分为一级缓存、二级缓存
    • 一级缓存:默认情况下开启一级缓存,sqlsession级别的缓存,也被称为本地缓存。
    • 二级缓存:默认关系,需要手动开启、配置,mapper(也可以认为是namespace)级别缓存,二级缓存也可以通过实现Cache接口自定义二级缓存。

二、一级缓存

1. 测试一级缓存

1. 同sqlsession一级缓存情况

测试代码:

public void testApp() throws IOException {
    // 1. Resources工具类,配置文件的加载,把配置文件加载成字节输入流
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析了配置文件,并创建sqlSessionFactory工厂
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 生产sqlSession
    SqlSession sqlSession = sessionFactory.openSession();// openSession(true)为自动开启事务,开启后可以不用使用commit()方法

    // 4. sqlSession调用方法:查询所有:selectList  查询单个:selectOne  添加:insert  修改:update 删除:delete

    // 查询
    List<User> users = sqlSession.selectList("com.zlt.mapper.UserMapper.findAll");
    System.out.println(users);

    List<User> users1 = sqlSession.selectList("com.zlt.mapper.UserMapper.findAll");
    System.out.println(users1);

    sqlSession.close();
}

输出结果:
在这里插入图片描述
结论:
根据一级缓存的作用范围是sqlsession级别,那么在同一个sqlsession会话中,查询同一条语句会有一级缓存,有缓存之后不会直接去数据库再次查询,减少与数据库的交互。

2. 不同sqlsession下一级缓存情况

测试代码:

public void testApp() throws IOException {
    // 1. Resources工具类,配置文件的加载,把配置文件加载成字节输入流
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析了配置文件,并创建sqlSessionFactory工厂
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 生产sqlSession
    SqlSession sqlSession1 = sessionFactory.openSession();// openSession(true)为自动开启事务,开启后可以不用使用commit()方法
    SqlSession sqlSession2 = sessionFactory.openSession();// openSession(true)为自动开启事务,开启后可以不用使用commit()方法

    // 4. sqlSession调用方法:查询所有:selectList  查询单个:selectOne  添加:insert  修改:update 删除:delete

    // 查询
    List<User> users = sqlSession1.selectList("com.zlt.mapper.UserMapper.findAll");
    System.out.println(users);

    List<User> users1 = sqlSession2.selectList("com.zlt.mapper.UserMapper.findAll");
    System.out.println(users1);
    
    sqlSession1.close();
    sqlSession2.close();
}

输出结果:
在这里插入图片描述
结论
很明显可以发现由于是两个不同的sqlsession,那么查询同一条SQL语句,一级缓存并不生效。

3. 一级缓存失效情况

测试代码:

public void testApp() throws IOException {
    // 1. Resources工具类,配置文件的加载,把配置文件加载成字节输入流
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析了配置文件,并创建sqlSessionFactory工厂
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 生产sqlSession
    SqlSession sqlSession = sessionFactory.openSession();// openSession(true)为自动开启事务,开启后可以不用使用commit()方法

    // 4. sqlSession调用方法:查询所有:selectList  查询单个:selectOne  添加:insert  修改:update 删除:delete

    // 查询
    List<User> users = sqlSession.selectList("com.zlt.mapper.UserMapper.findAll");
    System.out.println(users);
    User condition = new User();
    condition.setId(1);
    User oneByCondition = sqlSession.selectOne("com.zlt.mapper.UserMapper.findByCondition", condition);
    System.out.println(oneByCondition);

    // 新增
    User saveUser = new User();
    saveUser.setId(5);
    saveUser.setUserName("尼古拉");
    sqlSession.insert("com.zlt.mapper.UserMapper.save",saveUser);
    sqlSession.commit();
    System.out.println("新增数据成功");

    List<User> users1 = sqlSession.selectList("com.zlt.mapper.UserMapper.findAll");
    System.out.println(users1);
    User oneByCondition1 = sqlSession.selectOne("com.zlt.mapper.UserMapper.findByCondition", condition);
    System.out.println(oneByCondition1);

    sqlSession.close();
}

输出结果:
在这里插入图片描述
结论:
在同一个sqlsession会话中,数据但凡发生了修改,那么所有的一级缓存都会失效。


2. 源码解析

1. 一级缓存是什么

MyBatis中接口SqlSession接口一般都会存有重要的方法。可以发现其中和缓存沾边的就clearCache()方法。一路点击 SqlSession[clearCache()] -> DefaultSqlSession[clearCache()] -> Executor[clearLocalCache()] -> BsseExecutor[clearLocalCache()] -> PerpetualCache[clear()] 可以发现一级缓存底层就是HashMap,每一个SqlSEssion都会存放一个map对象的引用。

在这里插入图片描述


2. 一级缓存什么时候创建

一般来说,Executor用来执行SQL请求的,那么我们可以认为一级缓存也是在该接口Executor进行创建,所以可以查看Executor中的方法找寻以及缓存的创建。可以发现与一级缓存创建有关的方法 createCacheKey() 创建缓存key。

在这里插入图片描述

这里为缓存key创建方法

// 创建缓存key方法。
// ms:某一mapper映射器信息,包括了对应的id,传入参数,返回参数,configuration等。
// parameterObject:传入参数对象
// rowBounds:一般存放分页信息
// boundSql:绑定sql
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  // 创建一个缓存对象
  CacheKey cacheKey = new CacheKey();
  // 设置参数,mapper的id--namespace.id
  cacheKey.update(ms.getId());
  // 设置分页的参数
  cacheKey.update(rowBounds.getOffset());
  cacheKey.update(rowBounds.getLimit());
  // 设置转换后?的SQL语句参数
  cacheKey.update(boundSql.getSql());
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
  // mimic DefaultParameterHandler logic
  // 设置缓存key的传入参数属性
  for (ParameterMapping parameterMapping : parameterMappings) {
    if (parameterMapping.getMode() != ParameterMode.OUT) {
      Object value;
      String propertyName = parameterMapping.getProperty();
      if (boundSql.hasAdditionalParameter(propertyName)) {
        value = boundSql.getAdditionalParameter(propertyName);
      } else if (parameterObject == null) {
        value = null;
      } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
        value = parameterObject;
      } else {
        MetaObject metaObject = configuration.newMetaObject(parameterObject);
        value = metaObject.getValue(propertyName);
      }
      cacheKey.update(value);
    }
  }
  // 如果配置源中的环境不为空,那么也需要设置到缓存key中。
  if (configuration.getEnvironment() != null) {
    // issue #176
    cacheKey.update(configuration.getEnvironment().getId());
  }
  return cacheKey;
}

方法query()中有使用到创建缓存key的方法,并且创建的key有被使用查询。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

使用到缓存key的方法query()

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  // 如果查询栈的数量为0并且有缓存刷新的请求,那么清除本地的缓存
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
  	// 查询栈深度+1
    queryStack++;
    // 先从缓存中获取查询的数据
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      // 这里判断如果查询的SQL语句是存储过程,那么就会缓存输入、输出参数,提高存储过程的效率。
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // 如果缓存中获取的查询数据为空,那么就从数据库中获取需要查询的数据。
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
  	// 查询栈深度-1
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    // 如果配置中的缓存范围为语句级别的缓存,每一个Statement有自己的缓存,相互独立。那么查询之后需要删除缓存。
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

3. 一级缓存失效

点击查阅sqlsession中的insertupdatedelete方法,可以发现底层都是执行的是Executor接口的update()方法
在这里插入图片描述
查看实现Executor接口的BaseExecutor类中的update方法,很明显可以发现在每次更新数据前,都会将一级缓存(本地缓存)清空。
在这里插入图片描述

三、二级缓存

1. 测试二级缓存

1. 手动开启、配置二级缓存

sqlMapConfig.xml打开二级缓存配置

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>  

在这里插入图片描述
对应的mapper.xml中添加二级缓存开启标签,标签还有一些其他的属性,这里不赘述。

<cache></cache>

在这里插入图片描述
查询的实体类实现序列化接口Serializable
在这里插入图片描述

2. 二级缓存命中情况

测试代码:

public void test6() throws IOException {
    // 1. Resources工具类,配置文件的加载,把配置文件加载成字节输入流
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析了配置文件,并创建sqlSessionFactory工厂
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 生产sqlSession
    SqlSession sqlSession1 = sessionFactory.openSession();
    SqlSession sqlSession2 = sessionFactory.openSession();
    SqlSession sqlSession3 = sessionFactory.openSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

    List<User> all = mapper1.findAll();
    System.out.println("mapper1查询");
    sqlSession1.close();
    mapper2.findAll();
    System.out.println("mapper2查询");
    mapper3.findAll();
    System.out.println("mapper3查询");
}

输出结果:
在这里插入图片描述
结论:
三个不同的sqlsession调用同一个mapper中的同一个查询方法,除了第一次的查询需要和数据库进行交互,使用SQL查询,即便sqlsession已经关闭了。后面两次不同sqlsession查询的数据直接通过二级缓存获取。第二次查询的二级缓存命中率0.5即为1/2,第三次查询的二级缓存命中率为2/3。如果其中一个语句有修改对应的数据,那么二级缓存会失效,这里就不测试了。

3. 测试二级缓存的使用、刷新

可以在statement中设置是否开启二级缓存的使用,以及设置更新操作之后刷新二级缓存。useCacheflushCache

  • useCache:用来设置是否开启二级缓存,在statement中设置,默认为true,如果设置为false,那么每次查询都会发出sql查询。针对每次查询都需要最新的数据,要设置成true,直接从数据库中获取最新的数据。
  • flushCache:用来设置是否刷新二级缓存,在statement中设置,默认为true,如果设置为false则不会刷新。
    使用二级缓存的情况下,如果手动修改了数据库表中的数据,那么由于没有出发二级缓存的失效,会导致不会查询数据库中修改后的最新数据,导致出现脏读的情况。

useCache示例:
在mapper.xml层中添加useCache属性
在这里插入图片描述
测试代码:

public void test6() throws IOException {
    // 1. Resources工具类,配置文件的加载,把配置文件加载成字节输入流
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析了配置文件,并创建sqlSessionFactory工厂
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 生产sqlSession
    SqlSession sqlSession1 = sessionFactory.openSession();
    SqlSession sqlSession2 = sessionFactory.openSession();
    SqlSession sqlSession3 = sessionFactory.openSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

    List<User> all = mapper1.findAll();
    System.out.println("mapper1查询");
    sqlSession1.close();
    mapper2.findAll();
    System.out.println("mapper2查询");
    mapper3.findAll();
    System.out.println("mapper3查询");
}

输出结果:
在这里插入图片描述
结论
针对某条SQL的查询标签,设置了useCache为false之后,那么所有的查询都只会往数据库中进行查询,而不会使用二级缓存。

flushCache示例:
在mapper.xml层添加flushCache属性,对需要取消刷新缓存的更新操作标签添加flushCache属性,并设置为false

<测试代码:

public void test6() throws IOException {
    // 1. Resources工具类,配置文件的加载,把配置文件加载成字节输入流
    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    // 2. 解析了配置文件,并创建sqlSessionFactory工厂
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 3. 生产sqlSession
    SqlSession sqlSession1 = sessionFactory.openSession();
    SqlSession sqlSession2 = sessionFactory.openSession();
    SqlSession sqlSession3 = sessionFactory.openSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);

    mapper1.findAll();
    System.out.println("mapper1查询");
    sqlSession1.close();

    // 新增
    User saveUser = new User();
    saveUser.setId(5);
    saveUser.setUserName("尼古拉");
    sqlSession2.insert("com.zlt.mapper.UserMapper.save",saveUser);
    sqlSession2.commit();
    System.out.println("新增数据成功");

    mapper3.findAll();
    System.out.println("mapper3查询");
}

输出结果:
在这里插入图片描述
结论:
设置flushCache为false之后,对于更新的SQL语句,不会使缓存失效(一级缓存、二级缓存),后续的查询还是更新操作之前的数据。


2. 源码分析

根据二级缓存测试代码,我们需要关注三个问题。

  1. MyBatis是如何通过配置文件将需要二级缓存的SQL语句添加到缓存中的?
  2. 如果同时开启了一二级缓存,那么先缓存命中的顺序是什么?
  3. 为什么一定要执行sqlSession.close()或sqlSession.commit()方法后才能从命中二级缓存?
  4. 有更新操作时,怎么刷新二级缓存的?

① MyBatis是如何通过配置文件将需要二级缓存的SQL语句添加到缓存中的?

MyBatis的核心配置文件中的<settings>标签中添加了二级缓存是否开启的配置
在这里插入图片描述

在代码里,找到解析MyBatis核心配置文件解析的源码
在这里插入图片描述
build方法中有个解析的方法,这里将MyBatis核心配置文件解析后创建一个DefaultSqlSessionFactory对象。

在这里插入图片描述
MyBatis核心配置文件以<configuration>标签进行配置,所有的标签属性解析都在这里
在这里插入图片描述
<configuration>配置解析中有<settings>标签,这里将核心配置文件中配置的使用二级缓存进行解析,并将解析后的属性存入到Configuration类中。
在这里插入图片描述
可以清楚的发现,即使MyBatis核心配置文件中没有开启二级缓存设置,这里的默认也是开启的。默认为true。
在这里插入图片描述
二级缓存配置的第二步是需要在mapper.xml文件中配置<cache>标签后才会生效,这时候继续查找mapper.xml解析部分
在这里插入图片描述
继续查看org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration方法。
在这里插入图片描述
解析<mappers>时会创建一个专门解析mapper映射文件的对象–XMLMapperBuilder。并执行解析
在这里插入图片描述
根据核心配置文件配置的mapper路径,找到对应的mapper.xml文件解析。
在这里插入图片描述
找到配置中<cache>标签的解析方法,查看里面解析后的操作。
在这里插入图片描述
这里解析<cache>标签中的所有属性,包括了缓存的实现类、缓存容器大小、是否序列化等等。最后可以根据方法名userNewCache,大概可以猜到这个方法就是创建缓存的方法。
在这里插入图片描述
二级缓存的类型和一级缓存一样,根据上一步的缓存type得知缓存的类型为PERPETUAL,那么缓存的底层和一级缓存一样,都是PerpetualCache类,底层都是使用HashMap实现。这里生成缓存之后会在Configuration类中添加一份缓存外,还会在当前类org.apache.ibatis.builder.MapperBuilderAssistant中存储一份二级缓存。
在这里插入图片描述
在这里插入图片描述
设置二级缓存的第三步,是在对应的mapper.xml中各个增删改查节点标签中添加配置。
在这里插入图片描述
找到解析部分中的解析mapper.xml中各个增删改查标签节点。
在这里插入图片描述
在这里插入图片描述
这里会将mapper.xml中各个增删改查的节点进行遍历,并对每个增删改查节点创建一个MappedStatement的类。
在这里插入图片描述
找到org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode中解析<select>节点属性的useCache/flushCache属性,根据源码可以发现useCache默认为true,而flushCache属性默认为false。
在这里插入图片描述
根据设置的两个属性找到使用这两个属性的方法,最后一步将在增删改查标签节点获取到的所有属性封装创建MappedStatement类。
在这里插入图片描述
生成MappedStatement类时,会将标签中的配置设置的属性传入到MappedStatement生成,同时也将方法所在的类MapperBuilderAssistant存储的currentCache二级缓存赋值给MappedStatement,最后MappedStatement类设置到Configuration中。
在这里插入图片描述

总结:根据源码分析,解析二级缓存时,将Mapper中创建的Cache对象,加入到Configuration中,并加⼊到了每个MappedStatement对象中,也就是同⼀个Mapper对应的缓存都是一致的。都是MapperBuilderAssistant.currentCache

② 如果同时开启了一二级缓存,那么先缓存命中的顺序是什么?

关于查询语句执行后的缓存情况,直接点进查询语句进行翻阅
在这里插入图片描述
这里使用了mapper代理接口的方式进行查询,直接翻阅invoke方法,这里面就是代理方法实际执行的方法。
在这里插入图片描述

在这里插入图片描述
根据MappedStatement中的SQL语句类型select找到对应的执行方法。
在这里插入图片描述
底层实际上使用的是sqlSession.selectList()方法执行。
在这里插入图片描述
这里需要注意一下,由于使用了二级缓存,所以执行的Executor为CachingExecutor执行器执行。获取二级缓存使用的是TransactionCacheManager的getObject()方法。
在这里插入图片描述
TransactionCacheManager中有个重要的属性,transactionCaches这是个HashMap,其中的key是缓存cache,value值是类TransactionCache事务缓存。这里根据cache获取对应的TransactionCache
在这里插入图片描述
TransactionCache中的delegate属性为cache类,通过缓存key获取到对应缓存。
在这里插入图片描述
获取到二级缓存的结果,如果结果为空,那么执行查询,这里的delegate是引用的是执行器BaseExecutor。
在这里插入图片描述
执行基础执行器中的query方法,这里先查询一级缓存是否存在,存在则返回结果,不存在那么需要从数据库中获取结果,并将数据库中获取的结果存入到一级缓存中。最后将结果返回出去。
在这里插入图片描述
获取到从BaseExecutor基础执行器中的结果数据后,调用TransactionCacheManager中的putObject方法,将结果数据存入到二级缓存中
:这里的说明的二级缓存并不是名义上的二级缓存。具体往下看。
在这里插入图片描述
调用TransactionCache中的putObject方法。
在这里插入图片描述
这里有个map类型的对象:entriesToAddOnCommit,key值存入的缓存key。value值存放的是缓存数据。
会发现从数据库查询出来的数据并没有存入到二级缓存cache中。
在这里插入图片描述

总结:如果二级缓存与一级缓存同时开启的话,这时候查询语句先从二级事务缓存管理器TransctionCacheManager中获取对应的二级缓存(Cache)。如果不存在,那么执行基础的执行器BaseExecutort查询,这时候从一级缓存localCache中获取一级缓存,如果一级缓存无数据,那么就从数据库中获取对应的结果并存入到一级缓存localCache后返回,然后再给二级缓存赋值,这时候存入的是TranasctionCache中的entriesToAddOnCommit属性中,并不是二级缓存cache中,所以这时候需要引出为什么需要sqlSession.commit()才能命中二级缓存了。

③ 为什么一定要执行sqlSession.close()或sqlSession.commit()方法后才能从命中二级缓存?

直接点入sqlSession.commit()翻阅源码,首先使用执行器提交事务。
在这里插入图片描述
由于使用了缓存,这里使用的是CachingExecutor执行器。这时候又出现熟悉的属性tcm(TransctionCacheManager)
在这里插入图片描述
使用TransactionCacheManager执行提交操作时,现将其Map类型的transactionlCahce进行遍历,获取到所有的TransactionlCache对象,逐个提交。
在这里插入图片描述
TransactionlCache中有个刷新的方法flushPendingEntries(),这里会将TransactionlCache中所有的entriesToAddOnCommit刷新到二级缓存Cache中。
在这里插入图片描述
刷新操作分两步,第一步将存入的缓存entriesToAddOnCommit遍历数据都存入到二级缓存Cache中。第二步将未命中缓存的缓存key存入到set类型entriesMissedInCache属性中。
在这里插入图片描述
总结:sqlSession提交后,将事务缓存未提交的缓存刷新到二级缓存中。这时候二级缓存才生效。注意⼆级缓存是从 MappedStatement中获取的。由于 MappedStatement 存在于全局配置中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个事务共⽤⼀个缓存实例,会导致脏读问题。⾄于脏读问题,需要借助其他类来处理,也就是上⾯代码中
tcm 变量对应的类型TransactionCacheManager

TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是⼀种缓存装饰器,可以为 Cache 实例增加事务功能。我在之前提到的脏读问题正是由该类进⾏处理的。

存储⼆级缓存对象的时候是放到了TransactionalCache.entriesToAddOnCommit这个map中,但是每次查询的时候是直接从TransactionalCache.delegate中去查询的,所以这个⼆级缓存查询数据库后,设置缓存值是没有⽴刻⽣效的,主要是因为直接存到 delegate (二级缓存Cache)会导致脏数据问题。

④ 有更新操作时,怎么刷新二级缓存的?

直接查看更新操作源码。使用执行器进行更新操作。
在这里插入图片描述
使用了缓存执行器进行更新操作,其中flushCacheIfRequired()方法就是清空缓存的方法。
在这里插入图片描述
熟悉的属性tcm,依旧是调用该属性中的方法。
在这里插入图片描述
首先清空未提交的缓存数据。同时标记clearOnCommit,这个属性后面sqlSession提交时会用到。
在这里插入图片描述
清空“二级缓存”后再使用基础执行器执行更新方法。
在这里插入图片描述
基础执行器中需要将一级缓存数据清理进行清空。
在这里插入图片描述
二级缓存清空了吗,实际上并没有清空,只是清空了未提交的缓存数据,什么时候清空二级缓存数据呢?就是在sqlSession提交时清空。
在这里插入图片描述

在这里插入图片描述
底层还是将所有的TransactionlCache进行提交。
在这里插入图片描述
clearOnCommit这个属性眼熟吧,就是在update更新操作时设置为true的。这时候派上用场了,当为true时,删除delegate(二级缓存Cache)中的所有缓存数据。然后再刷新数据,由于未提交的数据在更新操作时已经清空,这时候刷新后的二级缓存已经为空。
在这里插入图片描述

总结

MyBatis⼆级缓存只适⽤于不常进⾏增、删、改的数据,⽐如国家⾏政区省市区街道数据。⼀但数据变更,MyBatis会清空缓存。因此⼆级缓存不适⽤于经常进⾏更新的数据。由于MyBatis二级缓存的局限性,一般来说不推荐使用MyBatis提供的二级缓存。

在⼆级缓存的设计上,MyBatis⼤量地运⽤了装饰者模式,如CachingExecutor, 以及各种Cache接⼝的装饰器。

  • ⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
  • ⼆级缓存具有丰富的缓存策略。
  • ⼆级缓存可由多个装饰器,与基础缓存组合⽽成
  • ⼆级缓存⼯作由 ⼀个缓存装饰执⾏器CachingExecutor和 ⼀个事务型预缓存TransactionalCache完成。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值