mybatis-3.4.6 缓存介绍

系列

  • mybatis-3.4.6 配置介绍
  • mybatis-3.4.6 顶层配置解析
  • mybatis-3.4.6 子配置解析
  • mybatis-3.4.6 mapper解析
  • mybatis-3.4.6 SQL执行流程
  • mybatis-3.4.6 SqlSession执行过程
  • mybatis-3.4.6 缓存介绍
  • mybatis-3.4.6 自增主键
  • mybatis-3.4.6 foreach 自增主键
  • mybatis-3.4.6 事务管理


开篇

  • 这个系列是基于mybatis-3.4.6版本的源码解析,这篇文章主要是梳理mybatis的一级缓存和二级缓存。

  • 核心主要是分析cache的使用范围以及对应的SQL执行对缓存的使用。


mybatis-3.4.6 缓存介绍_第1张图片
mybatis多级缓存
  • 1、一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。

  • 2、二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。

  • 3、对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

  • 4、从Cache的层面来说,二级缓存在一级缓存之前生效


mybatis一级缓存

public abstract class BaseExecutor implements Executor {
  // localCache作为mybatis的一级缓存
  protected PerpetualCache localCache;

  public  List 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 (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }

    List list;
    try {
      queryStack++;
      // 先从localCache查询
      list = resultHandler == null ? (List) localCache.getObject(key) : null;
      if (list != null) {
        // 如果已经存在则直接返回
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 缓存不存在就查询DB
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }

    return list;
  }


  private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List 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;
  }
}
  • mybatis一级缓存在SimpleExecutor级别的,BaseExecutor作为SimpleExecutor的父类。
  • BaseExecutor的localCache用来进行缓存,先查询缓存后查询DB,查询完DB保存到localCache当中。
public class PerpetualCache implements Cache {
  private final String id;
  private Map cache = new HashMap();
}
  • mybatis一级缓存是通过PerpetualCache来进行存储的。


mybatis二级缓存

public class CachingExecutor implements Executor {

  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 通过MappedStatement来进行查询
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List list = (List) tcm.getObject(cache, key);
        // 缓存不存在的情况下由SimpleExecutor来执行query
        if (list == null) {
          list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 缓存不允许的情况下由SimpleExecutor来执行query
    return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}
  • 二级缓存在CachingExecutor层被使用。
  • 二级缓存是MappedStatement级别的,从MappedStatement当中查询Cache对象。
  • 在允许使用二级缓存的场景下优先查询二级缓存,没有的情况下通过SimpleExecutor查询DB。
  • 二级缓存是由TransactionalCache进行管理的。
public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  private final Cache delegate;
  private boolean clearOnCommit;
  private final Map entriesToAddOnCommit;
  private final Set entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap();
    this.entriesMissedInCache = new HashSet();
  }

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

  • TransactionalCache的entriesToAddOnCommit负载保存二级缓存的数据。


mybatis二级缓存的MappedStatement

public final class MappedStatement {
  // 省略其他变量
  
  // 二级缓存的cache对象
  private Cache cache;
  // 是否使用二级缓存
  private boolean useCache;
}
  • MappedStatement内部保存是否使用二级缓存的配置信息。
public class XMLStatementBuilder extends BaseBuilder {

  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    // 针对SELECT的命令行
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 针对SELECT的命令默认是不刷新cache的
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 针对SELECT的命令默认是使用cache的
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
}
  • 在生成MappedStatement对象时候,针对select操作flushCache默认为false,useCache默认是开启。
public class XMLStatementBuilder extends BaseBuilder {

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      // 负责解析cache节点
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

  private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class 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);
    }
  }
}


public class MapperBuilderAssistant extends BaseBuilder {

  public Cache useNewCache(Class typeClass,
      Class 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;
  }
}
  • XMLStatementBuilder的cacheElement(context.evalNode("cache"))负责创建二级缓存的cache对象。
public class MapperBuilderAssistant extends BaseBuilder {

  public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class parameterType,
      String resultMap,
      Class resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        // 设置是否使用cache
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }
}
  • configuration.addMappedStatement(statement)负责保存MappedStatement对象。


参考文章

  • 源码参考
  • mybatis官网介绍
  • 深入理解mybatis原理
  • Mybatis3.4.x技术内幕
  • mybatis 3.x源码深度解析与最佳实践

你可能感兴趣的:(mybatis-3.4.6 缓存介绍)