• mybatis源码(十)mybatis二级缓存的使用


    mybatis源码(十)mybatis二级缓存的使用

    mybatis的二级缓存是mapper级别的缓存

    1.mybatis中如何使用二级缓存

      a.mybatis的主配置文件的settings中设置cacheEnabled=true

      b.mybatis的mapper的配置文件中,配置缓存策略、缓存刷新时间、缓存容量等属性.这个有专门的cache标签。

      c.在配置mapper时,通过usecache属性指定是否使用缓存、通过flushCache指定mapper执行后是否刷新缓存。

    2.mybatis二级缓存的实现原理

      2.1 mybatis二级缓存开启的地方

        mybatis的主配置文件的settings中设置cacheEnabled=true时,创建Executor的时候,默认是创建简单的执行器,这里替换成CacheExecutor,是执行器接口的一个实现类

         newExecutor 方法是在sqlSessionFactory.openSession()是调用的

      public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
      }
    
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        // 根据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);
        }
        // 如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        // 执行拦截器链的拦截逻辑
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
    

     代码执行到这里,我们的执行器已经不是默认的simple执行器了,而是CachingExecutor. 后续执行代码的时候,就是通过该执行器执行代码

      2.2 mybatis二级缓存的创建过程

        二级缓存的创建是在解析mapper配置文件的时候创建的。

      流程如下:

        XMLConfigBuilder:解析mybatis主配置文件--->解析mybatis的mapper配置文件---->解析cache标签---->创建缓存对象
      
    public class XMLConfigBuilder extends BaseBuilder {
    
      // 解析主配置文件    
      public Configuration parse() {
        // 防止parse()方法被同一个实例多次调用
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        // 调用XPathParser.evalNode()方法,创建表示configuration节点的XNode对象。
        // 调用parseConfiguration()方法对XNode进行处理
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    
      // 解析mybatis主配置文件下configuration对象
      private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    
    // 解析mapper标签
     private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            // 通过<package>标签指定包名
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              // 通过resource属性指定XML文件路径
              if (resource != null && url == null && mapperClass == null) {
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                // 通过url属性指定XML文件路径
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);
           // 解析具体的mapper文件 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // 通过class属性指定接口的完全限定名 Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
    // 解析具体的mapper文件
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
    mapperParser.parse();
    从这里进去XMLMapperBuilder,解析具体的mapper文件,代码如下:
      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();
        // 通过MapperBuilderAssiatant创建Cache builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }

     

    MapperBuilderAssistant

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

    如上面的代码所示,在获取<cache>标签的所有属性信息后,调用MapperBuilderAssistant对象的userNewCache()方法创建二级缓存实例,然后通过MapperBuilderAssistant的currentCache属性保存二级
    缓存对象的引用。在调用MapperBuilderAssistant对象的addMappedStatement()方法创建MappedStatement对象时会将当前命名空间对应的二级缓存对象的引用添加到MappedStatement对象中。

    下面是创建MappedStatement对象的关键代码:

    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) {
    
        if (unresolvedCacheRef) {
          throw new IncompleteElementException("Cache-ref not yet resolved");
        }
    
        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))
            .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; }

     2.3 mybaits的二级缓存的原理

     CacheExcutor的query操作源码如下:

    public class CachingExecutor implements Executor {
    
      private final Executor delegate;
      private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    
      public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
      }
    
        @Override
        public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
          BoundSql boundSql = ms.getBoundSql(parameterObject);
          // 调用createCacheKey()方法创建缓存Key
          CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
          return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        }
    
        @Override
        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);
              // 从MappedStatement对象对应的二级缓存中获取数据
              @SuppressWarnings("unchecked")
              List<E> list = (List<E>) tcm.getObject(cache, key);
              if (list == null) {
                // 如果缓存数据不存在,则从数据库中查询数据
                list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                // 將数据存放到MappedStatement对象对应的二级缓存中
                tcm.putObject(cache, key, list); // issue #578 and #116
              }
              return list;
            }
          }
          return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        }
    }
    

    如上面的代码所示,在CachingExecutor的query()方法中, 首先调用createCacheKey()方法创建缓存Key对象,然后调用MappedStatement对象的getCache()方法获取MappedStatement对象
    中维护的二级缓存对象。然后尝试从二级缓存对象中获取结果,如果获取不到,则调用目标Executor对象的query()方法从数据库获取数据,再将数据添加到二级缓存中。

    由上代码可知,二级缓存是从TransactionalCacheManager中获取的,该类的源码如下:

    public class TransactionalCacheManager {
      // 通过HashMap对象维护二级缓存对应的TransactionalCache实例
      private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
    
      public void clear(Cache cache) {
        getTransactionalCache(cache).clear();
      }
    
      public Object getObject(Cache cache, CacheKey key) {
        // 获取二级缓存对应的TransactionalCache对象,然后根据缓存Key获取缓存对象
        return getTransactionalCache(cache).getObject(key);
      }
      
      public void putObject(Cache cache, CacheKey key, Object value) {
        getTransactionalCache(cache).putObject(key, value);
      }
    
      public void commit() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
          txCache.commit();
        }
      }
    
      public void rollback() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
          txCache.rollback();
        }
      }
    
      private TransactionalCache getTransactionalCache(Cache cache) {
        // 获取二级缓存对应的TransactionalCache对象
        TransactionalCache txCache = transactionalCaches.get(cache);
        if (txCache == null) {
          // 如果获取不到则创建,然后添加到Map中
          txCache = new TransactionalCache(cache);
          transactionalCaches.put(cache, txCache);
        }
        return txCache;
      }
    
    }
    

     

     2.4 mybaits的二级缓存删除地方

     当执行update更新操作的时候,同一命名空间下的二级缓存会被清空,下面是CachingExecutor的update方法

      @Override
      public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        // 如果需要刷新,则更新缓存
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
      }
    
      public boolean isFlushCacheRequired() {
        return flushCacheRequired;
      }
    
      private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {      
          tcm.clear(cache);
        }
      }
    

      

     

     

     
    
    


     

     

      

  • 相关阅读:
    dubbo里面的JavaBeanDescriptor是怎么进行序列化和反序列化的?
    为什么dubbo的调用重试不建议设置成超过1
    dubbo中registry、route、directory、cluster、loadbalance、route的关系以及一个引用操作和调用操作到底干了啥
    技术博客-1 DRF框架下的图片(文件)上传
    Scrum meeting 1
    beta设计和计划
    事后分析$alpha$
    项目展示$alpha$
    帮助文档
    发布声明α
  • 原文地址:https://www.cnblogs.com/yingxiaocao/p/13690545.html
Copyright © 2020-2023  润新知