Mybatis架构原理(二)-二级缓存源码剖析

二级缓存构建在一级缓存之上,在收到查询请求时,Mybatis首先会查询二级缓存,若二级缓存没有命中,再去查询一级缓存,一级缓存没有,在查询数据库;

二级缓存-->一级缓存-->数据库

与一级缓存不同,二级缓存和具体命名空间绑定,一个mapper中有一个cache,相同mapper中的mappedStatement共用一个Cache,一级缓存则是和sqlSession绑定;

启用二级缓存
  • 开启全局二级缓存配置:

    <settings>
    <setting name = "cacheEnabled" value = "true"/>
    </settings>
  • 在需要使用二级缓存的Mapper配置文件中配置<cache>标签

    <cache></cache>
  • 在具体CRUD标签上配置useCache = true

    <select id = "getById" resultType = "com.yun.pojo.User" useCache = "true">
    select * from user where id = #{id}
    </select>
标签 <cache/>解析
    //调用的重载方法
   public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
       try {
           // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
           XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
           // 执行 XML 解析
           // 创建 DefaultSqlSessionFactory 对象
           return build(parser.parse());
      } catch (Exception e) {
           throw ExceptionFactory.wrapException("Error building SqlSession.", e);
      } finally {
           ErrorContext.instance().reset();
           try {
               inputStream.close();
          } catch (IOException e) {
               // Intentionally ignore. Prefer previous error.
          }
      }
  }

--> parse()
   //解析 XML 成 Configuration 对象。
   public Configuration parse() {
       // 若已解析,抛出 BuilderException 异常
       if (parsed) {
           throw new BuilderException("Each XMLConfigBuilder can only be used once.");
      }
       // 标记已解析
       parsed = true;
       ///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
       // 解析 XML configuration 节点
       parseConfiguration(parser.evalNode("/configuration"));
       return configuration;
  }

--> parseConfiguration()
   //具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
   private void parseConfiguration(XNode root) {
       try {
           //issue #117 read properties first
           // 解析 <properties /> 标签
           propertiesElement(root.evalNode("properties"));
           // 解析 <settings /> 标签
           Properties settings = settingsAsProperties(root.evalNode("settings"));
           // 加载自定义的 VFS 实现类
           loadCustomVfs(settings);
           // 解析 <typeAliases /> 标签
           typeAliasesElement(root.evalNode("typeAliases"));
           // 解析 <plugins /> 标签
           pluginElement(root.evalNode("plugins"));
           // 解析 <objectFactory /> 标签
           objectFactoryElement(root.evalNode("objectFactory"));
           // 解析 <objectWrapperFactory /> 标签
           objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
           // 解析 <reflectorFactory /> 标签
           reflectorFactoryElement(root.evalNode("reflectorFactory"));
           // 赋值 <settings /> 到 Configuration 属性
           settingsElement(settings);
           // read it after objectFactory and objectWrapperFactory issue #631
           // 解析 <environments /> 标签
           environmentsElement(root.evalNode("environments"));
           // 解析 <databaseIdProvider /> 标签
           databaseIdProviderElement(root.evalNode("databaseIdProvider"));
           // 解析 <typeHandlers /> 标签
           typeHandlerElement(root.evalNode("typeHandlers"));
           // 解析 <mappers /> 标签
           mapperElement(root.evalNode("mappers"));
      } catch (Exception e) {
           throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
      }
  }

--> mapperElement()
   private void mapperElement(XNode parent) throws Exception {
       if (parent != null) {
           // 遍历子节点
           for (XNode child : parent.getChildren()) {
               // 如果是 package 标签,则扫描该包
               if ("package".equals(child.getName())) {
                   // 获取 <package> 节点中的 name 属性
                   String mapperPackage = child.getStringAttribute("name");
                   // 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置
                   configuration.addMappers(mapperPackage);
               // 如果是 mapper 标签,
              } else {
                   // 获得 resource、url、class 属性
                   String resource = child.getStringAttribute("resource");
                   String url = child.getStringAttribute("url");
                   String mapperClass = child.getStringAttribute("class");

                   // resource 不为空,且其他两者为空,则从指定路径中加载配置
                   if (resource != null && url == null && mapperClass == null) {
                       ErrorContext.instance().resource(resource);
                       // 获得 resource 的 InputStream 对象
                       InputStream inputStream = Resources.getResourceAsStream(resource);
                       // 创建 XMLMapperBuilder 对象
                       XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                       // 执行解析
                       mapperParser.parse();
                       // url 不为空,且其他两者为空,则通过 url 加载配置
                  } else if (resource == null && url != null && mapperClass == null) {
                       ErrorContext.instance().resource(url);
                       // 获得 url 的 InputStream 对象
                       InputStream inputStream = Resources.getUrlAsStream(url);
                       // 创建 XMLMapperBuilder 对象
                       XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                       // 执行解析
                       mapperParser.parse();
                       // mapperClass 不为空,且其他两者为空,则通过 mapperClass 解析映射配置
                  } else if (resource == null && url == null && mapperClass != null) {
                       // 获得 Mapper 接口
                       Class<?> mapperInterface = Resources.classForName(mapperClass);
                       // 添加到 configuration 中
                       configuration.addMapper(mapperInterface);
                       // 以上条件不满足,则抛出异常
                  } else {
                       throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                  }
              }
          }
      }
  }

--> parse()
   public void parse() {
       // 判断当前 Mapper 是否已经加载过
       if (!configuration.isResourceLoaded(resource)) {
           // 解析 `<mapper />` 节点
           configurationElement(parser.evalNode("/mapper"));
           // 标记该 Mapper 已经加载过
           configuration.addLoadedResource(resource);
           // 绑定 Mapper
           bindMapperForNamespace();
      }

       // 解析待定的 <resultMap /> 节点
       parsePendingResultMaps();
       // 解析待定的 <cache-ref /> 节点
       parsePendingCacheRefs();
       // 解析待定的 SQL 语句的节点
       parsePendingStatements();
  }

--> configurationElement()
   // 解析 `<mapper />` 节点
   private void configurationElement(XNode context) {
       try {
           // 获得 namespace 属性
           String namespace = context.getStringAttribute("namespace");
           if (namespace == null || namespace.equals("")) {
               throw new BuilderException("Mapper's namespace cannot be empty");
          }
           // 设置 namespace 属性
           builderAssistant.setCurrentNamespace(namespace);
           // 解析 <cache-ref /> 节点
           cacheRefElement(context.evalNode("cache-ref"));
           // 解析 <cache /> 节点
           cacheElement(context.evalNode("cache"));
           // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
           parameterMapElement(context.evalNodes("/mapper/parameterMap"));
           // 解析 <resultMap /> 节点们
           resultMapElements(context.evalNodes("/mapper/resultMap"));
           // 解析 <sql /> 节点们
           sqlElement(context.evalNodes("/mapper/sql"));
           // 解析 <select /> <insert /> <update /> <delete /> 节点们
           // 这里会将生成的Cache包装到对应的MappedStatement
           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);
      }
  }

--> cacheElement()
   //创建 Cache 对象
   public Cache useNewCache(Class<? extends Cache> typeClass,
                            Class<? extends Cache> evictionClass,
                            Long flushInterval,
                            Integer size,
                            boolean readWrite,
                            boolean blocking,
                            Properties props) {

       // 1.生成Cache对象
       Cache cache = new CacheBuilder(currentNamespace)
               //这里如果我们定义了<cache/>中的type,就使用自定义的Cache,否则使用和一级缓存相同的PerpetualCache
              .implementation(valueOrDefault(typeClass, PerpetualCache.class))
              .addDecorator(valueOrDefault(evictionClass, LruCache.class))
              .clearInterval(flushInterval)
              .size(size)
              .readWrite(readWrite)
              .blocking(blocking)
              .properties(props)
              .build();
       // 2.添加到Configuration中
       configuration.addCache(cache);
       // 3.并将cache赋值给MapperBuilderAssistant.currentCache
       currentCache = cache;
       return cache;
  }

--> buildStatementFromContext()
   // 解析 <select /> <insert /> <update /> <delete /> 节点们
   private void buildStatementFromContext(List<XNode> list) {
       if (configuration.getDatabaseId() != null) {
           buildStatementFromContext(list, configuration.getDatabaseId());
      }
       buildStatementFromContext(list, null);
       // 上面两块代码,可以简写成 buildStatementFromContext(list, configuration.getDatabaseId());
  } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
       //遍历 <select /> <insert /> <update /> <delete /> 节点们
       for (XNode context : list) {
           // 创建 XMLStatementBuilder 对象,执行解析
           final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
           try {

               // 每一条执行语句转换成一个MappedStatement
               statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
               // 解析失败,添加到 configuration 中
               configuration.addIncompleteStatement(statementParser);
          }
      }
  }

--> parseStatementNode()
   //执行解析
   public void parseStatementNode() {
       // 获得 id 属性,编号。
       String id = context.getStringAttribute("id");
       // 获得 databaseId , 判断 databaseId 是否匹配
       String databaseId = context.getStringAttribute("databaseId");
       if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
           return;
      }

       // 获得各种属性
       Integer fetchSize = context.getIntAttribute("fetchSize");
       Integer timeout = context.getIntAttribute("timeout");
       String parameterMap = context.getStringAttribute("parameterMap");
       String parameterType = context.getStringAttribute("parameterType");
       Class<?> parameterTypeClass = resolveClass(parameterType);
       String resultMap = context.getStringAttribute("resultMap");
       String resultType = context.getStringAttribute("resultType");
       String lang = context.getStringAttribute("lang");

       // 获得 lang 对应的 LanguageDriver 对象
       LanguageDriver langDriver = getLanguageDriver(lang);

       // 获得 resultType 对应的类
       Class<?> resultTypeClass = resolveClass(resultType);
       // 获得 resultSet 对应的枚举值
       String resultSetType = context.getStringAttribute("resultSetType");
       ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
       // 获得 statementType 对应的枚举值
       StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

       // 获得 SQL 对应的 SqlCommandType 枚举值
       String nodeName = context.getNode().getNodeName();
       SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
       // 获得各种属性
       boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
       boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
       boolean useCache = context.getBooleanAttribute("useCache", isSelect);
       boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

       // Include Fragments before parsing
       // 创建 XMLIncludeTransformer 对象,并替换 <include /> 标签相关的内容
       XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
       includeParser.applyIncludes(context.getNode());

       // Parse selectKey after includes and remove them.
       // 解析 <selectKey /> 标签
       processSelectKeyNodes(id, parameterTypeClass, langDriver);

       // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
       // 创建 SqlSource 对象
       SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
       // 获得 KeyGenerator 对象
       String resultSets = context.getStringAttribute("resultSets");
       String keyProperty = context.getStringAttribute("keyProperty");
       String keyColumn = context.getStringAttribute("keyColumn");
       KeyGenerator keyGenerator;
       // 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 <selectKey /> 标签配置的
       String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
       keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
       if (configuration.hasKeyGenerator(keyStatementId)) {
           keyGenerator = configuration.getKeyGenerator(keyStatementId);
       // 其次,根据标签属性的情况,判断是否使用对应的 Jdbc3KeyGenerator 或者 NoKeyGenerator 对象
      } else {
           keyGenerator = context.getBooleanAttribute("useGeneratedKeys", // 优先,基于 useGeneratedKeys 属性判断
                   configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) // 其次,基于全局的 useGeneratedKeys 配置 + 是否为插入语句类型
                   ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      }

       // 创建 MappedStatement 对象
       builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
               fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
               resultSetTypeEnum, flushCache, useCache, resultOrdered,
               keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

-->addMappedStatement()
   // 构建 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) {

       // 如果只想的 Cache 未解析,抛出 IncompleteElementException 异常
       if (unresolvedCacheRef) {
           throw new IncompleteElementException("Cache-ref not yet resolved");
      }

       // 获得 id 编号,格式为 `${namespace}.${id}`
       id = applyCurrentNamespace(id, false);
       boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

       // 创建 MappedStatement.Builder 对象
       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)) // 获得 ResultMap 集合
              .resultSetType(resultSetType)
              .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
              .useCache(valueOrDefault(useCache, isSelect))
              .cache(currentCache); // 在这里将之前生成的Cache封装到MappedStatement

       // 获得 ParameterMap ,并设置到 MappedStatement.Builder 中
       ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
       if (statementParameterMap != null) {
           statementBuilder.parameterMap(statementParameterMap);
      }

       // 创建 MappedStatement 对象
       MappedStatement statement = statementBuilder.build();
       // 添加到 configuration 中
       configuration.addMappedStatement(statement);
       return statement;
  }
   

我们看到将Mapper中创建的Cache对象,加入到了每个MappedStatement对象中,也就是同一个mapper中所有的MappedStatement中的cache属性引用是同一个;


CachingExecutor(源码验证一级缓存和二级缓存同时开启的状态下,在进行查询时,按照二级缓存-->一级缓存-->数据库顺序执行)
 User user = sqlSession1.selectOne("com.yun.mapper.IUserMapper.findById", 1);

-->
   public <T> T selectOne(String statement, Object parameter) {
       // Popular vote was to return null on 0 results and throw exception on too many.
       List<T> list = this.selectList(statement, parameter);
       if (list.size() == 1) {
           return list.get(0);
      } else if (list.size() > 1) {
           throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
      } else {
           return null;
      }
  }

-->selectList()
   public <E> List<E> selectList(String statement, Object parameter) {
       return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
       try {
           // 获得 MappedStatement 对象
           MappedStatement ms = configuration.getMappedStatement(statement);
           // 执行查询
           return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      } catch (Exception e) {
           throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
      } finally {
           ErrorContext.instance().reset();
      }
  }

-->query() 先走CachingExecutor
   @Override
   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
           throws SQLException {

       // 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
       // 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
       // 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
       Cache cache = ms.getCache();

       // 如果配置文件中没有配置 <cache>,则 cache 为空
       if (cache != null) {
           //如果需要刷新缓存的话就刷新:flushCache="true"
           flushCacheIfRequired(ms);
           if (ms.isUseCache() && resultHandler == null) {
               // 暂时忽略,存储过程相关
               ensureNoOutParams(ms, boundSql);
               @SuppressWarnings("unchecked")
               // 从二级缓存中,获取结果
               List<E> list = (List<E>) tcm.getObject(cache, key);
               if (list == null) {
                   // 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
                   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);
  }

-->query()再走BaseExecutor
   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());
       // 已经关闭,则抛出 ExecutorException 异常
       if (closed) {
           throw new ExecutorException("Executor was closed.");
      }
       // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
       if (queryStack == 0 && ms.isFlushCacheRequired()) {
           clearLocalCache();
      }
       List<E> list;
       try {
           // queryStack + 1
           queryStack++;
           // 从一级缓存中,获取查询结果
           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 - 1
           queryStack--;
      }
       if (queryStack == 0) {
           // 执行延迟加载
           for (DeferredLoad deferredLoad : deferredLoads) {
               deferredLoad.load();
          }
           // issue #601
           // 清空 deferredLoads
           deferredLoads.clear();
           // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
           if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
               // issue #482
               clearLocalCache();
          }
      }
       return list;
  }

-->queryFromDatabase()
   // 从数据库中读取操作
   private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
       List<E> list;
       // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
       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;
  }

-->queryFromDatabase()走完回到CachingExecutor的query()方法中进入 putObject()
   进入putObject()方法之前先看一下 TransactionalCacheManager类,他是事务缓存管理器,他内部维护了
   private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();这么一个cache实例和TransactionalCache实例的一个映射关系,真正做事的是TransactionalCache,为什么我们之前已经获取到Cache这个对象了,不用cache反而用transactionalCaches呢?  那是因为我们现在的二级缓存是从MAppedStatement中获取的.

下面我们看看TransactionalCache主要是做些什么的

打开TransactionalCacheManager源码可以看到下面两个方法

    /**
    * 获得缓存中,指定 Cache + K 的值。
    */
   public Object getObject(Cache cache, CacheKey key) {
       // 直接从TransactionalCache中获取缓存
       return getTransactionalCache(cache).getObject(key);
  }

-->getTransactionalCache()
   /**
    * 获得 Cache 对应的 TransactionalCache 对象
    */
   private TransactionalCache getTransactionalCache(Cache cache) {
       return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

   /**
    * 添加 Cache + KV ,到缓存中
    */
   public void putObject(Cache cache, CacheKey key, Object value) {
       // 直接存入TransactionalCache的缓存中
       getTransactionalCache(cache).putObject(key, value);
  }

进入TransactionalCache查看getObject()和putObject()两个方法之间的作用

    /**
    * 委托的 Cache 对象。
    * 实际上,就是二级缓存 Cache 对象。
    */
   private final Cache delegate;
   /**
    * 提交时,清空 {@link #delegate}
    * 初始时,该值为 false
    * 清理后{@link #clear()} 时,该值为 true ,表示持续处于清空状态
    */
   private boolean clearOnCommit;
   /**
    * 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
    */
   private final Map<Object, Object> entriesToAddOnCommit;
   /**
    * 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
    */
   private final Set<Object> entriesMissedInCache;    

public Object getObject(Object key) {
       // 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询
       Object object = delegate.getObject(key);
       // 如果不存在,则添加到 entriesMissedInCache 中
       if (object == null) {
           // 缓存未命中,则将 key 存入到 entriesMissedInCache 中
           entriesMissedInCache.add(key);
      }
       // issue #146
       // 如果 clearOnCommit 为 true ,表示处于持续清空状态,则返回 null
       if (clearOnCommit) {
           return null;
       // 返回 value
      } else {
           return object;
      }
  }
   
   public void putObject(Object key, Object object) {
       // 将键值对存入到 entriesToAddOnCommit 这个Map中中,而非真实的缓存对象 delegate 中
       entriesToAddOnCommit.put(key, object);
  }

TransactionalCache实现了cache接口,第一次存储的时候是存到了entriesToAddOnCommit集合中,但是取的时候,却是从delegate二级缓存中取,因此我们可以得知是无论如何都获取不到的,这也是引出了在测试方法中,为什么第一次查询和第二次查询之间要commit一下了;

@Test
 public void test3() throws IOException {

   InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
   SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

   SqlSession sqlSession1 = factory.openSession();
   SqlSession sqlSession2 = factory.openSession();

   User user1 = sqlSession1.selectOne("com.yun.mapper.IUserMapper.findById", 1);
   System.out.println(user1);

   sqlSession1.commit();

   User user = new User();
   user.setId(1);
   user.setUsername("jack");
   // 增删改会清空二级缓存
   sqlSession1.update("com.yun.mapper.IUserMapper.updateById",user);


   User user2 = sqlSession2.selectOne("com.yun.mapper.IUserMapper.findById", 1);
   System.out.println(user2);

}

TransactionalCache类里面有一个commit方法

public void commit() {
       // 如果 clearOnCommit 为 true ,则清空 delegate 缓存
       if (clearOnCommit) {
           delegate.clear();
      }
       // 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate(cache) 中
       flushPendingEntries();
       // 重置
       reset();
  }

/**
    * 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
    */
   private void flushPendingEntries() {
       // 将 entriesToAddOnCommit 中的内容转存到 delegate 中
       for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {

           // 在这里真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时,二级缓存才真正的生效
           delegate.putObject(entry.getKey(), entry.getValue());
      }
       // 将 entriesMissedInCache 刷入 delegate 中
       for (Object entry : entriesMissedInCache) {
           if (!entriesToAddOnCommit.containsKey(entry)) {
               delegate.putObject(entry, null);
          }
      }
  }

可以看到它里面的flushPendingEntries()就是将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate(cache) 中;

这样就能解释我们在像entriesToAddOnCommit这个集合中存完数据后,下一次从二级缓存对象delegate中获取数据之前,需要让commit()执行一次,那么entriesToAddOnCommit里面的内容才会真正的存到delegate这个对象中,这样二级缓存中才会有数据.不直接存到delegate中是因为缓存中可能会存在脏数据问题,所以需要先存到entriesToAddOnCommit中去;

Mybatis架构原理(二)-二级缓存源码剖析的更多相关文章

  1. Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析

    像Mybatis.Hibernate这样的ORM框架,封装了JDBC的大部分操作,极大的简化了我们对数据库的操作. 在实际项目中,我们发现在一个事务中查询同样的语句两次的时候,第二次没有进行数据库查询 ...

  2. select用法&原理详解(源码剖析)(转)

    今天遇到了在select()前后fd_set的变化问题,查了好久终于找到一个有用的帖子了,很赞,很详细!!原文链接如下: select用法&原理详解(源码剖析) 我的问题是: 如下图示:在se ...

  3. Mybatis工作原理(含部分源码)

    MyBatis的初始化 1.读取配置文件,形成InputStream String resource = "mybatis.xml"; // 加载mybatis的配置文件(它也加载 ...

  4. Spring缓存源码剖析:(一)工具选择

    从本篇开始对Spring 4.3.6版本中Cache部分做一次深度剖析.剖析过程中会对其中使用到的设计模式以及原则进行分析.相信对设计内功修炼必定大有好处. 一.环境及工具 IntelliJ IDEA ...

  5. Spring缓存源码剖析:(二)CacheManager

    一.CacheManager总览 如果需要Spring缓存可以正常工作,必须配置一个CacheManager. CacheManager实现类你可以配置Spring-context本身提供的Simpl ...

  6. mybatis结合redis实战二级缓存(六)

    之前的文章中我们意见分析了一级缓存.二级缓存的相关源码和基本原理,今天我们来分享下了mybatis二级缓存和redis的结合,当然mybatis二级缓存也可以和ehcache.memcache.OSC ...

  7. mybatis结合redis实战二级缓存

    之前的文章中我们意见分析了一级缓存.二级缓存的相关源码和基本原理,今天我们来分享下了mybatis二级缓存和redis的结合,当然mybatis二级缓存也可以和ehcache.memcache.OSC ...

  8. 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析

    通过前面的学习我们已经掌握了Volley的基本用法,没看过的建议大家先去阅读我的博文[安卓网络请求开源框架Volley源码解析系列]初识Volley及其基本用法.如StringRequest用来请求一 ...

  9. jdk源码剖析:Synchronized

    开启正文之前,先说一下源码剖析这一系列,就以"死磕到底"的精神贯彻始终,最少追踪到JVM指令(再往下C语言实现了). =========正文分割线===========  Sync ...

随机推荐

  1. SpringMVC-组件分析之视图解析器(prefix,suffix)

    SpringMVC的默认组件都是在DispatcherServlet.properties配置文件中配置的: spring-webmvc->org/springframewrok/web/ser ...

  2. k8s pod 在迁移zookeeper时出现的问题

    一次迁移中出现的问题,因为要搬迁机房,集群中的节点服务器分布在两个机房,通过专线打通了,现在需要整体都迁移到其中一个机房,所以pod要进行迁移,机器资源也比较紧张,在迁移中zookeeper迁移出现问 ...

  3. linux权限与系统信息

    权限 1.权限分为3个部分 可读(r) 可写(w) 可执行(x) 没有对应权限(-) 2.权限位 权限位主要分为三个部分,分别是属主.属组以及其他人 rwx : 属主 r-x : 属组 r-x : 其 ...

  4. allure用法(一)-配置信息及基本用法

    allure是一个轻量级的,灵活的,支持多语言的测试报告工具 优点: 可以为dev/qa 提供 详尽的测试报告.测试步骤.日志 可以为管理层提供更好的统计报告 Java语言开发的 可以集成到jenki ...

  5. Go Slice Tricks Cheat Sheet、Go 切片使用小妙招

    AppendVector. Copy. Cut. Delete. Delete without preserving order. Cut (GC). Delete (GC). Delete with ...

  6. B. Lord of the Values 思维数学建构 附加 英文翻译

    原题链接 Problem - 1523B - Codeforces 题目及部分翻译 While trading on(贸易,利用) his favorite exchange trader Willi ...

  7. 【Azure API 管理】解决API Management添加AAD Group时遇见的 Failed to query Azure Active Directory graph due to error 错误

    问题描述 为APIM添加AAD Group时候,等待很长很长的时间,结果添加失败.错误消息为: Write Groups ValidationError :Failed to query Azure ...

  8. 2021.07.20 P3951 小凯的疑惑(最大公因数,未证)

    2021.07.20 P3951 小凯的疑惑(最大公因数,未证) 重点: 1.最大公因数 题意: 求ax+by最大的表示不了的数(a,b给定 x,y非负). 分析: 不会.--2021.07.20 代 ...

  9. css兼容问题集锦

    BEGIN; 1.文本框很大,导致里面的内容不居中.以及内容为数字时,不支持text-indent属性 解:line-height: K px; (值为文本框的height值). 2.文本框有背景图片 ...

  10. 人机验证reCAPTCHA v3使用完备说明

    v2简介 相信大家都碰到过下面的展示的 人机验证界面: reCaptcha 是 Google 公司的验证码服务,方便快捷,改变了传统验证码需要输入n位失真字符的特点. reCaptcha 在使用的时候 ...