mybatis source code analysis 04: cache

Note: This series of source code analysis is based on mybatis 3.5.6, the source code gitee warehouse warehouse address: funcy/mybatis .

This article will analyze the cache of mybatis from the perspective of source code.

mybatisIn the sql execution process analyzed earlier , CachingExecutortwo caches will be encountered along the way:

  • CachingExecutor#query(...)When executing, it will first judge whether it exists in the cache, and call the specific Executorquery when it does not exist.
  • BaseExecutor#query(...)When executed, it will be localCacheobtained from it first, and will be queried from the database when it does not exist

Let's take the sql execution process in the previous article as an example, and analyze these two caches step by step.

1. mybatisLevel 1 cache

Let's get into the BaseExecutor#query(...)method:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, 
        ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
    List<E> list;
    try {
      queryStack++;
      // 1. 直接从缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 从数据库中获取
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // 2. 缓存范围为STATEMENT时,会清除缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        // 清除数据库中的记录
        clearLocalCache();
      }
    }
    return list;
  }
复制代码

This method is a bit long, but we only need to focus on two places:

  • localCacheGet data directly from the cache ( )
  • When the cache range STATEMENTis , the cache will be cleared after each query, that is, the data will not be cached

1.1 What is the first level cache

First let's see what it localCacheis:

public abstract class BaseExecutor implements Executor {
  ...
  /**
   * 这就是 localCache
   */
  protected PerpetualCache localCache;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    ...
    // 在这里赋值  
    this.localCache = new PerpetualCache("LocalCache");
  }

  ...

复制代码

This localCacheis PerpetualCache, we continue:

public class PerpetualCache implements Cache {

  private final String id;

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

  public PerpetualCache(String id) {
    this.id = id;
  }

  /**
   * 设置缓存的操作
   */
  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  /**
   * 获取缓存的操作
   */
  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  /**
   * 移除缓存的操作
   */
  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  /**
   * 清空缓存
   */
  @Override
  public void clear() {
    cache.clear();
  }

  ...
}
复制代码

PerpetualCacheIt is the processing of packaging Map, and operations such as adding, acquiring, and removing caches are actually correct Mapoperations.

Here we understand that the so-called first-level cache ( localCache) is one Map, and records are stored in memory. Note: here Mapis HashMap, is not thread safe.

1.2 Update timing of the first level cache

In the BaseExecutor#query(...)method, if the cache is not hit, it will be queried from the database. Let's take a look at the query process of the database:

  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);
    }
    // 设置一级缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
复制代码

代码可以看的很清楚了,在处理查询(doQuery(...))后,会手动移除一级缓存(localCache.removeObject(...)),并设置一级缓存(localCache.putObject(...)).

当然了,在update操作中也有类似的处理缓存的操作,这里就不一一看了。

1.3 一级缓存的作用范围及禁用

一级缓存中,缓存数据的MapHashMap,并非线程安全的,因此一级缓存并非线程安全的。那么它的作用范围呢?

通过代码的追溯,localCacheExecutor的成员变量,而Executor又是DefaultSqlSession的成员变量。在前面的分析中,DefaultSqlSession并不是线程安全的,比较合理的方式是为每个线程新建一个DefaultSqlSession,至此可以推断出一级缓存(localCache)的作用范围为SqlSession,保存在内存中。

由于一级缓存的作用范围为SqlSession,因此使用时可能会导致数据库更新了,但缓存还没变。

举例说明下:

// 配置文件路径
String resource = "org/apache/ibatis/demo/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
// 使用 sqlSession01 获取 userMapper
SqlSession sqlSession01 = null;
SqlSession sqlSession02 = null;
try {
  sqlSession01 = factory.openSession(true);
  sqlSession02 = factory.openSession(true);
  UserMapper userMapper01 = sqlSession01.getMapper(UserMapper.class);
  // 使用 sqlSession02 获取 userMapper
  UserMapper userMapper02 = sqlSession02.getMapper(UserMapper.class);

  // 处理查询与更新操作,顺序很重要
  // 使用 userMapper01 查询
  List<User> users = userMapper01.selectList(3L, 10);
  System.out.println("userMapper01 得到的users: " + users);
  // 使用 sqlSession02 更新
  int result = userMapper02.updateUser(3L, "HelloWorld");
  System.out.println("更新的记录数: " + result);
  // 使用 sqlSession02 查询
  List<User> users2 = userMapper02.selectList(3L, 10);
  System.out.println("userMapper02 得到的users: " + users2);
  // 使用 sqlSession01 查询
  List<User> users01 = userMapper01.selectList(3L, 10);
  System.out.println("userMapper01 得到的users: " + users01);
} finally {
  // 省略关闭操作
  ...
}
复制代码

以上代码先是获取了两个SqlSession,然后分别从这两个SqlSession得到UserMapper的实例userMapper01userMapper02,然后我们使用这两个UserMapper实例进行操作:

  1. 使用userMapper01查询id为3的记录
  2. 使用userMapper02更新id为3的记录
  3. 使用userMapper02查询id为3的记录
  4. 使用userMapper01查询id为3的记录

运行,得到的结果如下:

userMapper01 得到的users: [User{id=3, loginName='test', nick='test'}]
更新的记录数: 1
userMapper02 得到的users: [User{id=3, loginName='test', nick='HelloWorld'}]
userMapper01 得到的users: [User{id=3, loginName='test', nick='test'}]
复制代码

从运行结果来看,userMapper02更新成功了,userMapper02也能查到更新后数据了,但userMapper01第二次查到的依然是更新前的记录,这也就是说,使用userMapper01第一次查询时,记录被缓存了,第二次查询时,并没有查询数据库,而是直接从缓存里获取数据了。

从以上示例可以看到,开启一级缓存后,并不能查到实时数据,并且一级缓存并没有提供对外操作的入口,启用后极有可能会产生严重后果,那么我们要怎么关闭它呢?

mybatis文档的settings节点介绍中,有一个属性可以解决这个问题:

我们在配置文件中这样设置:

<configuration>
  <properties resource="org/apache/ibatis/demo/config.properties">
  </properties>
  <settings>
    <!-- 设置一级缓存级别 -->
    <setting name="localCacheScope" value="STATEMENT"/>
  </settings>
  ...
</configuration>
复制代码

这样设置之后,一级缓存就不再缓存数据了。关于这样做的原理,就得回到BaseExecutor#query(...)方法了:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, 
        ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
    if (queryStack == 0) {
      ...
      // 2. 缓存范围为STATEMENT时,会清除缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // 清除数据库中的记录
        clearLocalCache();
      }
    }
    return list;
  }

  /**
   * 清除缓存的操作
   */
  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
复制代码

从源码来看,将localCacheScope设置为STATEMENT后,每次查询完成后,都会调用localCache.clear()来清除缓存了。

2. mybatis二级缓存

接下来我们再来看看二级缓存。

2.1 开启二级缓存

在创建执行器时,mybatis在创建完具体的执行器后,会再缓存执行器:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 根据类型创建具体的执行器
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    // 缓存装饰
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 处理 plugin(插件)
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
复制代码

在之后处理sql的查询/更新操作时,都是调用CachingExecutor进行,比如查询操作:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, 
        ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache();
    // 操作缓存
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        // 从缓存中获取
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // 实际处理查询的操作
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 添加到缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 不使用缓存,直接处理查询的操作
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
复制代码

这里的Cache cache = ms.getCache()就是用来操作二级缓存的。默认情况下,二级缓存并未开启:

可以看到,cachenull,表示并未启用二级缓存。那么该如何启用呢?mybatis文档告诉了我们启用方式:

我们在UserMapper.xml中添加<cache/>标签:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.demo.mapper.UserMapper">

  <cache/>
  ...
</mapper>
复制代码

再次执行查询时,就会发现cache已经不为null了:

2.2 配置二级缓存

mybatis文档中,二级缓存还有其他的配置,这里也一并贴出来:

这些参数本文就不细讲了,我们来看下这些参数是在哪里解析的。

<cache/>标签定义在mapper.xml文件中,我们当然就想到它应该是在解析mapper.xml文件时解析到的,我们进入<mapper>标签的解析方法XMLMapperBuilder#configurationElement

  private void configurationElement(XNode context) {
    try {
      ...
      // 解析二级缓存标签
      cacheElement(context.evalNode("cache"));
      ...
    } catch (Exception e) {
      ...
    }
  }
复制代码

继续进入XMLMapperBuilder#cacheElement方法:

  private void cacheElement(XNode context) {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      // 构建Cache对象
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, 
            readWrite, blocking, props);
    }
  }

复制代码
  • 缓存的默认实现类为PerpetualCache,我们也可以指定type为我们自己的实现类,下节再说明
  • 缓存过期的默认策略为LruCache
  • readOnly默认值为false
  • blocking默认值为false
  • size默认值为null
  • flushInterval默认值为null

我们继续进入MapperBuilderAssistant#useNewCache看看cache的构建过程:

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 构建缓存
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    // 添加缓存
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }
复制代码

这个方法主要是调用CacheBuilder来构建Cache,我们进入CacheBuilder#build方法:

public Cache build() {
    // 设置默认的实现
    setDefaultImplementations();
    // 实例化,反射实例化
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // 使用的是默认缓存才需要装饰
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      // 设置标准的装饰器,用来处理eviction,flushInterval,size,readOnly等配置
      cache = setStandardDecorators(cache);
    } 
    // 如果不是 PerpetualCache,不会处理eviction,flushInterval,size,readOnly等配置
    else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

  /**
   * 设置缓存的默认实现类
   */
  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

  /**
   * 处理标准装饰器
   * @param cache
   * @return
   */
  private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        // 定其清理
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        // 序列化
        cache = new SerializedCache(cache);
      }
      // 日志
      cache = new LoggingCache(cache);
      // 同步
      cache = new SynchronizedCache(cache);
      // 阻塞
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException(...);
    }
  }
复制代码

默认情况下,缓存的实现类为PerpetualCache,对于默认的缓存,实例化后,会根据配置的参数进行相关的装饰操作,如配置了clearInterval参数,就会使用ScheduledCache进行装饰:

  @Override
  public void putObject(Object key, Object object) {
    clearWhenStale();
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    return clearWhenStale() ? null : delegate.getObject(key);
  }

  private boolean clearWhenStale() {
    // 判断是否超时
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }
复制代码

ScheduledCache的功能是定期清理缓存,在putget操作时,都会判断是否超时,如果超时了就会触发清理操作。

mybatiscache实现及装饰器如下:

可以看到,真正的实现只有PerpetualCache,其余的均为装饰器。

来看看得到的Cache

由于层层装饰,cache的层次有点多,最底层的cachePerpetualCache,这是真正干活的类,它的idorg.apache.ibatis.demo.mapper.UserMapper,也就是Mapper接口的包名.类名

我们继续看看configuration.addCache(cache)操作:

  protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");

  public void addCache(Cache cache) {
    caches.put(cache.getId(), cache);
  }
复制代码

这一步的操作就是把cache保存到caches中了。注意到caches类型为StrictMap,稍微看下它的定义:

 protected static class StrictMap<V> extends HashMap<String, V> {
   ...
 }
复制代码

就是HashMap的子类了。

这一步得到的caches的结果如下:

这一步得到cache后,在后面解析select|insert|update|delete语句时,会把得到的当前得到的cache一并放入到MappedStatement对象中,相关操作为MapperBuilderAssistant#addMappedStatement(...)方法,就不过多分析了。

2.3 TransactionalCacheManager

再回到CachingExecutor#query(...)方法,处理操作的方法如下:

public class CachingExecutor implements Executor {
  /**
   * 缓存管理
   */
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, 
        ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 这个就是 PerpetualCache 装饰后的对象
    Cache cache = ms.getCache();
    // 操作缓存
    if (cache != null) {
        // 从缓存中获取
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          ...
          // 添加到缓存中
          tcm.putObject(cache, key, list);
        }
        ...
    }
    ...
  }
  ...
}
复制代码

可以看到,得到cache后,使用TransactionalCacheManager进行操作,我们进入TransactionalCacheManager看看相关方法:

public class TransactionalCacheManager {

  // 也是一个Map,保存的是 TransactionalCache
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  public Object getObject(Cache cache, CacheKey key) {

    return getTransactionalCache(cache).getObject(key);
  }

  /**
   * 从transactionalCaches 中获取 TransactionalCache,如果不存在则新建
   */
  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }
  ...
}
复制代码

关于TransactionalCache,它也是Cache的实现类:

public class TransactionalCache implements Cache {

  /**
   * 真正的缓存操作类
   */
  private final Cache delegate;
  
  private final Set<Object> entriesMissedInCache;
  ...

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.entriesToAddOnCommit = new HashMap<>();
    ...
  }

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public void putObject(Object key, Object object) {
    // 先添加到临时缓存中
    entriesToAddOnCommit.put(key, object);
  }

  /**
   * 处理事务的提交操作
   * 事务提交时,才把临时缓存中的缓存添加到缓存中
   */
  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    // 更新缓存
    flushPendingEntries();
    reset();
  }

  /**
   * 更新缓存
   */
  private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }
  ...

}
复制代码

这个类实现了如下功能:

  1. 它也是个包装类,实现了缓存的事务功能
  2. 添加对象时,会行添加到临时缓存中,当事务提交后,才会真正添加到二级缓存中
  3. 对象的移除也是类似的操作,只有当事务提交后才会真正操作二级缓存

2.4 使用自定义二级缓存

从上面的分析来看,mybatis为每个mapper.xmlnamespace)都创建了一个缓存对象(默认实现类为PerpetualCache),与MappedStatement绑定在一起,因此二级缓存的作用范围为全局,而且对于每个namespace都对应一个缓存对象。

二级缓存的默认实现为PerpetualCache,我们来看看它的内容:

public class PerpetualCache implements Cache {

  ...

  // 用hashmap来保存记录
  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);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  ...
}
复制代码

从代码来看,PerpetualCache其中维护了一个名为cache的成员变量,它是HashMap类型的,缓存的添加/获取/清除等都是操作这个HashMapmybatis的二级缓存同样也是保存在内存中的!

HashMap并不是线程安全的,我们在多线程环境下能使用二级缓存的默认实现(PerpetualCache)吗?答案是可以,虽然HashMap不是线程安全的,但经过缓存装饰器之后,二级缓存的添加/获取/清除等操作就是线程安全了,这就是SynchronizedCache的功劳:

public class SynchronizedCache implements Cache {

  // 被装饰的对象,也是真正干活的对象
  private final Cache delegate;

  ...

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  ...
}
复制代码

只是简单地在添加/获取/清除等操作方法上加了synchronized关键字,至于性能方面就呵呵了。

为了更好地发挥二级缓存的功能,mybatis可以让开发者自定义二级缓存的实现:

注意,如果使用了自定义缓存,那么evictionflushIntervalsizereadOnly等配置将不会生效,这点在CacheBuilder#build方法中也能得到体现:

在自定义缓存时,我们可以使用redis等分布式缓存来存储数据,这里就不多分析了。

2.5 何时使用二级缓存

分析完了二级缓存,什么情况下适合使用二级缓存(mybits的默认实现)呢?

从前面的分析来看,二级缓存的默认实现有以下特点:

  • 作用范围为全局(整个jvm进程)
  • 保存在内存中(不支持分布式)
  • 并发性能低(操作方法添加了synchronized关键字)

结合以上所述,mybatis提供的二级缓存只 适合在单机、对并发要求不高的情况下使用。

需要注意的是,以上条件是针对mybats提供的二级缓存默认实现,我们还可以自定义二级缓存,以实现其在分布式、高并发环境的使用条件。

3. 一二级缓存特性汇总

3.1 缓存的配置汇总

cacheEnabled

在 mybatis 配置文件中,有一个settings节点,下面有一配置为cacheEnabled

Which cache is this cacheEnabledon or off?

Let's go back to the Configuration#newExecutor(...)method again:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    ...
    // 缓存装饰
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    ...
  }
复制代码

This cacheEnabledis the value settingsunder the node cacheEnabled. From the code point of view, when it is cacheEnabledtimefalse , CachingExecutorit will not CachingExecutorbe used to deal with the second-level cache, so the second-level cache cannot be used, even if the tag mapper.xmlis added , the second-level cache cannot be used. <cache/>.

Is that level 1 cache still usable? From the previous analysis, the operation of the second-level cache is in BaseExecutor, so it will not be affected here.

3.2 mybatis cache comparison

cache type Scope Save location Is it thread safe? performance Whether to support distributed
Level 1 session Memory no high no
Level 2 (default implementation) global Memory Yes Low no

3.3 Reasonable use of mybatis cache

How to use the cache reasonably mybatis? My suggestion is:

  1. For L1 cache, localCacheScopeset to STATEMENT;
  2. For the second-level cache, if the mybatisdefault one is used, it can be used in the case of a single machine and low concurrency requirements, and must not be used in the case of multiple machines; if you use a custom cache, you can freely implement the cache according to the usage scenario.

The link to the original text of this article: my.oschina.net/funcy/blog/… , limited to the author's personal level, there are inevitably mistakes in the text, welcome to correct me! Originality is not easy. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

Guess you like

Origin juejin.im/post/7102811317605498894