mybatisソースコード分析04:キャッシュ

注:この一連のソースコード分析は、mybatis 3.5.6、ソースコードgitee倉庫倉庫アドレス:funcy/mybatisに基づいています。

この記事では、ソースコードの観点からmybatisのキャッシュを分析します。

以前に分析されmybatisたSQL実行プロセスCachingExecutorでは、途中で2つのキャッシュが発生します。

  • CachingExecutor#query(...)実行時に、最初にキャッシュに存在するかどうかを判断し、存在しない場合は特定のExecutorクエリを呼び出します。
  • BaseExecutor#query(...)実行されると、localCache最初にデータベースから取得され、存在しない場合はデータベースから照会されます。

前の記事のSQL実行プロセスを例として取り上げ、これら2つのキャッシュを段階的に分析してみましょう。

1.mybatisレベル1キャッシュ

メソッドに入りましょうBaseExecutor#query(...)

  @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;
  }
复制代码

この方法は少し長いですが、2つの場所に焦点を当てるだけで済みます。

  • キャッシュから直接localCacheデータを取得する()
  • キャッシュ範囲STATEMENTがの場合、各クエリの後にキャッシュがクリアされます。つまり、データはキャッシュされません。

1.1第1レベルのキャッシュとは

まず、それが何であるかを見てみましょうlocalCache

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

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

  ...

复制代码

これlocalCachePerpetualCache、続けます:

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();
  }

  ...
}
复制代码

PerpetualCacheこれはパッケージ化の処理でありMap、キャッシュの追加、取得、削除などの操作は実際には正しいMap操作です。

ここでは、いわゆる第1レベルのキャッシュ(localCache)が1つMapであり、レコードがメモリに格納されていることを理解しています。注:これMapHashMap、スレッドセーフではありません。

1.2第1レベルのキャッシュの更新タイミング

このBaseExecutor#query(...)メソッドでは、キャッシュがヒットしなかった場合、データベースからクエリが実行されます。データベースのクエリプロセスを見てみましょう。

  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

これはどのキャッシュをcacheEnabledオンまたはオフにしますか?

Configuration#newExecutor(...)もう一度メソッドに戻りましょう。

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

これcacheEnabledsettings、ノードの下の値cacheEnabledです。コードの観点から見ると、cacheEnabled時間falseCachingExecutorなるCachingExecutorと、第2レベルのキャッシュを処理するために使用されるため、タグmapper.xmlが追加されても第2レベルのキャッシュは使用できません。キャッシュは使用できません<cache/>

そのレベル1のキャッシュはまだ使用できますか?前の分析から、第2レベルのキャッシュの操作はにBaseExecutorあるので、ここでは影響を受けません。

3.2mybatisキャッシュの比較

キャッシュタイプ 範囲 位置を保存 スレッドセーフですか? パフォーマンス 分散をサポートするかどうか
レベル1 セッション メモリー 番号 高い 番号
レベル2(デフォルトの実装) グローバル メモリー はい 低い 番号

3.3mybatisキャッシュの合理的な使用

キャッシュを合理的に使用する方法はmybatis私の提案は:

  1. L1キャッシュの場合、;にlocalCacheScope設定します。STATEMENT
  2. 第2レベルのキャッシュの場合、mybatisデフォルトのキャッシュを使用すると、単一のマシンで同時実行性の要件が低い場合に使用でき、複数のマシンの場合は使用できません。カスタムキャッシュを使用する場合は、使用シナリオに応じてキャッシュを自由に実装できます。

この記事の元のテキストへのリンク:my.oschina.net/funcy/blog/…、著者の個人的なレベルに限定されており、テキストには必然的に間違いがあります。私を訂正することを歓迎します!オリジナリティは簡単ではありません。商用の再版の場合は、著者に許可を求めてください。非商用の再版の場合は、出典を示してください。

おすすめ

転載: juejin.im/post/7102811317605498894