美文网首页mybatis
mybatis-3.4.6 缓存介绍

mybatis-3.4.6 缓存介绍

作者: 晴天哥_王志 | 来源:发表于2020-07-23 21:34 被阅读0次

    系列

    开篇

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

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

    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 <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 (queryStack == 0 && ms.isFlushCacheRequired()) {
          clearLocalCache();
        }
    
        List<E> list;
        try {
          queryStack++;
          // 先从localCache查询
          list = resultHandler == null ? (List<E>) 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 <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;
      }
    }
    
    • mybatis一级缓存在SimpleExecutor级别的,BaseExecutor作为SimpleExecutor的父类。
    • BaseExecutor的localCache用来进行缓存,先查询缓存后查询DB,查询完DB保存到localCache当中。
    public class PerpetualCache implements Cache {
      private final String id;
      private Map<Object, Object> cache = new HashMap<Object, Object>();
    }
    
    • mybatis一级缓存是通过PerpetualCache来进行存储的。

    mybatis二级缓存

    public class CachingExecutor implements Executor {
    
      public <E> List<E> 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<E> list = (List<E>) tcm.getObject(cache, key);
            // 缓存不存在的情况下由SimpleExecutor来执行query
            if (list == null) {
              list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        // 缓存不允许的情况下由SimpleExecutor来执行query
        return delegate.<E> 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<Object, Object> entriesToAddOnCommit;
      private final Set<Object> entriesMissedInCache;
    
      public TransactionalCache(Cache delegate) {
        this.delegate = delegate;
        this.clearOnCommit = false;
        this.entriesToAddOnCommit = new HashMap<Object, Object>();
        this.entriesMissedInCache = new HashSet<Object>();
      }
    
      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<? 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);
        }
      }
    }
    
    
    public class MapperBuilderAssistant extends BaseBuilder {
    
      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;
      }
    }
    
    • 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-3.4.6 缓存介绍

        本文链接:https://www.haomeiwen.com/subject/xtxhlktx.html