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

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

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

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

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

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

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

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

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

  34. --> parseConfiguration()
  35.    //具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
  36.    private void parseConfiguration(XNode root) {
  37.        try {
  38.            //issue #117 read properties first
  39.            // 解析 <properties /> 标签
  40.            propertiesElement(root.evalNode("properties"));
  41.            // 解析 <settings /> 标签
  42.            Properties settings = settingsAsProperties(root.evalNode("settings"));
  43.            // 加载自定义的 VFS 实现类
  44.            loadCustomVfs(settings);
  45.            // 解析 <typeAliases /> 标签
  46.            typeAliasesElement(root.evalNode("typeAliases"));
  47.            // 解析 <plugins /> 标签
  48.            pluginElement(root.evalNode("plugins"));
  49.            // 解析 <objectFactory /> 标签
  50.            objectFactoryElement(root.evalNode("objectFactory"));
  51.            // 解析 <objectWrapperFactory /> 标签
  52.            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  53.            // 解析 <reflectorFactory /> 标签
  54.            reflectorFactoryElement(root.evalNode("reflectorFactory"));
  55.            // 赋值 <settings /> 到 Configuration 属性
  56.            settingsElement(settings);
  57.            // read it after objectFactory and objectWrapperFactory issue #631
  58.            // 解析 <environments /> 标签
  59.            environmentsElement(root.evalNode("environments"));
  60.            // 解析 <databaseIdProvider /> 标签
  61.            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  62.            // 解析 <typeHandlers /> 标签
  63.            typeHandlerElement(root.evalNode("typeHandlers"));
  64.            // 解析 <mappers /> 标签
  65.            mapperElement(root.evalNode("mappers"));
  66.       } catch (Exception e) {
  67.            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  68.       }
  69.   }

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

  87.                    // resource 不为空,且其他两者为空,则从指定路径中加载配置
  88.                    if (resource != null && url == null && mapperClass == null) {
  89.                        ErrorContext.instance().resource(resource);
  90.                        // 获得 resource 的 InputStream 对象
  91.                        InputStream inputStream = Resources.getResourceAsStream(resource);
  92.                        // 创建 XMLMapperBuilder 对象
  93.                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  94.                        // 执行解析
  95.                        mapperParser.parse();
  96.                        // url 不为空,且其他两者为空,则通过 url 加载配置
  97.                   } else if (resource == null && url != null && mapperClass == null) {
  98.                        ErrorContext.instance().resource(url);
  99.                        // 获得 url 的 InputStream 对象
  100.                        InputStream inputStream = Resources.getUrlAsStream(url);
  101.                        // 创建 XMLMapperBuilder 对象
  102.                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  103.                        // 执行解析
  104.                        mapperParser.parse();
  105.                        // mapperClass 不为空,且其他两者为空,则通过 mapperClass 解析映射配置
  106.                   } else if (resource == null && url == null && mapperClass != null) {
  107.                        // 获得 Mapper 接口
  108.                        Class<?> mapperInterface = Resources.classForName(mapperClass);
  109.                        // 添加到 configuration 中
  110.                        configuration.addMapper(mapperInterface);
  111.                        // 以上条件不满足,则抛出异常
  112.                   } else {
  113.                        throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  114.                   }
  115.               }
  116.           }
  117.       }
  118.   }

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

  130.        // 解析待定的 <resultMap /> 节点
  131.        parsePendingResultMaps();
  132.        // 解析待定的 <cache-ref /> 节点
  133.        parsePendingCacheRefs();
  134.        // 解析待定的 SQL 语句的节点
  135.        parsePendingStatements();
  136.   }

  137. --> configurationElement()
  138.    // 解析 `<mapper />` 节点
  139.    private void configurationElement(XNode context) {
  140.        try {
  141.            // 获得 namespace 属性
  142.            String namespace = context.getStringAttribute("namespace");
  143.            if (namespace == null || namespace.equals("")) {
  144.                throw new BuilderException("Mapper's namespace cannot be empty");
  145.           }
  146.            // 设置 namespace 属性
  147.            builderAssistant.setCurrentNamespace(namespace);
  148.            // 解析 <cache-ref /> 节点
  149.            cacheRefElement(context.evalNode("cache-ref"));
  150.            // 解析 <cache /> 节点
  151.            cacheElement(context.evalNode("cache"));
  152.            // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
  153.            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  154.            // 解析 <resultMap /> 节点们
  155.            resultMapElements(context.evalNodes("/mapper/resultMap"));
  156.            // 解析 <sql /> 节点们
  157.            sqlElement(context.evalNodes("/mapper/sql"));
  158.            // 解析 <select /> <insert /> <update /> <delete /> 节点们
  159.            // 这里会将生成的Cache包装到对应的MappedStatement
  160.            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  161.       } catch (Exception e) {
  162.            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  163.       }
  164.   }

  165. --> cacheElement()
  166.    //创建 Cache 对象
  167.    public Cache useNewCache(Class<? extends Cache> typeClass,
  168.                             Class<? extends Cache> evictionClass,
  169.                             Long flushInterval,
  170.                             Integer size,
  171.                             boolean readWrite,
  172.                             boolean blocking,
  173.                             Properties props) {

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

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

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

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

  224.        // 获得各种属性
  225.        Integer fetchSize = context.getIntAttribute("fetchSize");
  226.        Integer timeout = context.getIntAttribute("timeout");
  227.        String parameterMap = context.getStringAttribute("parameterMap");
  228.        String parameterType = context.getStringAttribute("parameterType");
  229.        Class<?> parameterTypeClass = resolveClass(parameterType);
  230.        String resultMap = context.getStringAttribute("resultMap");
  231.        String resultType = context.getStringAttribute("resultType");
  232.        String lang = context.getStringAttribute("lang");

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

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

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

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

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

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

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

  282. -->addMappedStatement()
  283.    // 构建 MappedStatement 对象
  284.    public MappedStatement addMappedStatement(
  285.            String id,
  286.            SqlSource sqlSource,
  287.            StatementType statementType,
  288.            SqlCommandType sqlCommandType,
  289.            Integer fetchSize,
  290.            Integer timeout,
  291.            String parameterMap,
  292.            Class<?> parameterType,
  293.            String resultMap,
  294.            Class<?> resultType,
  295.            ResultSetType resultSetType,
  296.            boolean flushCache,
  297.            boolean useCache,
  298.            boolean resultOrdered,
  299.            KeyGenerator keyGenerator,
  300.            String keyProperty,
  301.            String keyColumn,
  302.            String databaseId,
  303.            LanguageDriver lang,
  304.            String resultSets) {

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

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

  312.        // 创建 MappedStatement.Builder 对象
  313.        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
  314.               .resource(resource)
  315.               .fetchSize(fetchSize)
  316.               .timeout(timeout)
  317.               .statementType(statementType)
  318.               .keyGenerator(keyGenerator)
  319.               .keyProperty(keyProperty)
  320.               .keyColumn(keyColumn)
  321.               .databaseId(databaseId)
  322.               .lang(lang)
  323.               .resultOrdered(resultOrdered)
  324.               .resultSets(resultSets)
  325.               .resultMaps(getStatementResultMaps(resultMap, resultType, id)) // 获得 ResultMap 集合
  326.               .resultSetType(resultSetType)
  327.               .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
  328.               .useCache(valueOrDefault(useCache, isSelect))
  329.               .cache(currentCache); // 在这里将之前生成的Cache封装到MappedStatement

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

  335.        // 创建 MappedStatement 对象
  336.        MappedStatement statement = statementBuilder.build();
  337.        // 添加到 configuration 中
  338.        configuration.addMappedStatement(statement);
  339.        return statement;
  340.   }
  341.    

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


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

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

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

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

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

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

  38.        // 如果配置文件中没有配置 <cache>,则 cache 为空
  39.        if (cache != null) {
  40.            //如果需要刷新缓存的话就刷新:flushCache="true"
  41.            flushCacheIfRequired(ms);
  42.            if (ms.isUseCache() && resultHandler == null) {
  43.                // 暂时忽略,存储过程相关
  44.                ensureNoOutParams(ms, boundSql);
  45.                @SuppressWarnings("unchecked")
  46.                // 从二级缓存中,获取结果
  47.                List<E> list = (List<E>) tcm.getObject(cache, key);
  48.                if (list == null) {
  49.                    // 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
  50.                    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  51.                    // 缓存查询结果(将查询结果再次存放缓存中,但并不是存放到二级缓存中)
  52.                    tcm.putObject(cache, key, list); // issue #578 and #116
  53.               }
  54.                // 如果存在,则直接返回结果
  55.                return list;
  56.           }
  57.       }
  58.        // 不使用缓存,则从数据库中查询(会查一级缓存)
  59.        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  60.   }

  61. -->query()再走BaseExecutor
  62.    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  63.        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  64.        // 已经关闭,则抛出 ExecutorException 异常
  65.        if (closed) {
  66.            throw new ExecutorException("Executor was closed.");
  67.       }
  68.        // 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
  69.        if (queryStack == 0 && ms.isFlushCacheRequired()) {
  70.            clearLocalCache();
  71.       }
  72.        List<E> list;
  73.        try {
  74.            // queryStack + 1
  75.            queryStack++;
  76.            // 从一级缓存中,获取查询结果
  77.            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  78.            // 获取到,则进行处理
  79.            if (list != null) {
  80.                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  81.            // 获得不到,则从数据库中查询
  82.           } else {
  83.                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  84.           }
  85.       } finally {
  86.            // queryStack - 1
  87.            queryStack--;
  88.       }
  89.        if (queryStack == 0) {
  90.            // 执行延迟加载
  91.            for (DeferredLoad deferredLoad : deferredLoads) {
  92.                deferredLoad.load();
  93.           }
  94.            // issue #601
  95.            // 清空 deferredLoads
  96.            deferredLoads.clear();
  97.            // 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
  98.            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
  99.                // issue #482
  100.                clearLocalCache();
  101.           }
  102.       }
  103.        return list;
  104.   }

  105. -->queryFromDatabase()
  106.    // 从数据库中读取操作
  107.    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  108.        List<E> list;
  109.        // 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
  110.        localCache.putObject(key, EXECUTION_PLACEHOLDER);
  111.        try {
  112.            // 执行读操作
  113.            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  114.       } finally {
  115.            // 从缓存中,移除占位对象
  116.            localCache.removeObject(key);
  117.       }
  118.        // 添加到缓存中(也就是添加到一级缓存中)
  119.        localCache.putObject(key, list);
  120.        // 暂时忽略,存储过程相关
  121.        if (ms.getStatementType() == StatementType.CALLABLE) {
  122.            localOutputParameterCache.putObject(key, parameter);
  123.       }
  124.        return list;
  125.   }

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

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

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

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

  8. -->getTransactionalCache()
  9.    /**
  10.     * 获得 Cache 对应的 TransactionalCache 对象
  11.     */
  12.    private TransactionalCache getTransactionalCache(Cache cache) {
  13.        return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  14.   }

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

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

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

  20. public Object getObject(Object key) {
  21.        // 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询
  22.        Object object = delegate.getObject(key);
  23.        // 如果不存在,则添加到 entriesMissedInCache 中
  24.        if (object == null) {
  25.            // 缓存未命中,则将 key 存入到 entriesMissedInCache 中
  26.            entriesMissedInCache.add(key);
  27.       }
  28.        // issue #146
  29.        // 如果 clearOnCommit 为 true ,表示处于持续清空状态,则返回 null
  30.        if (clearOnCommit) {
  31.            return null;
  32.        // 返回 value
  33.       } else {
  34.            return object;
  35.       }
  36.   }
  37.    
  38.    public void putObject(Object key, Object object) {
  39.        // 将键值对存入到 entriesToAddOnCommit 这个Map中中,而非真实的缓存对象 delegate 中
  40.        entriesToAddOnCommit.put(key, object);
  41.   }

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

  1. @Test
  2.  public void test3() throws IOException {

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

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

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

  9.    sqlSession1.commit();

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


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

  17. }

TransactionalCache类里面有一个commit方法

  1. public void commit() {
  2.        // 如果 clearOnCommit 为 true ,则清空 delegate 缓存
  3.        if (clearOnCommit) {
  4.            delegate.clear();
  5.       }
  6.        // 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate(cache) 中
  7.        flushPendingEntries();
  8.        // 重置
  9.        reset();
  10.   }

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

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

可以看到它里面的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. springboot+mybatis实现数据分页(三种方式)

    项目准备 1.创建用户表 2.使用spring初始化向导快速创建项目,勾选mybatis,web,jdbc,driver 添加lombok插件 <?xml version="1.0&q ...

  2. 动态代理-JDK

    代理模式:假设一个场景,你的公司是一位软件公司,你是一位软件工程师,显然客户带着需求不会去找你谈,而是去找商务谈,此时商务就代表公司. 商务的作用:商务可以谈判:也有可能在开发软件之前就谈失败,此时商 ...

  3. victoriaMetrics之byteBuffer

    victoriaMetrics之byteBuffer VictoriaMetrics经常会处理数目庞大的指标,在处理的过程中会涉及指标的拷贝,如果在指标拷贝时都进行内存申请的话,其内存消耗和性能损耗都 ...

  4. Python Json分别存入Mysql、MongoDB数据库,使用Xlwings库转成Excel表格

    将电影数据 data.json 数据通过xlwings库转换成excel表格,存入mysql,mongodb数据库中.python基础语法.xlwings库.mysql库.pymongo库.mongo ...

  5. 使用 HTML5 input 类型提升移动端输入体验(转翻译)

    在过去的几年里,在移动设备上浏览网页已变得难以置信的受欢迎. 但是这些设备上的浏览体验,有时遗留很多的有待改进.当涉及到填写表单时,这一点尤为明显.幸运的是,HTML5规范引入了许多新input类型, ...

  6. 邮件html页编写指南

    前言 写过邮件的html一般都用table来布局,为什么呢?原因是大多数的邮件客户端(比如Outlook和Gmail),会过滤经过多次的邮件编写实践及度娘的指导,我发现,编写自制兼容outlook与f ...

  7. Go 语言 结构体链表

    @ 目录 1. 什么是链表 2. 单项链表的基本操作 3. 使用 struct 定义单链表 4. 尾部添加节点 5. 头部插入节点 6. 指定节点后添加新节点 7. 删除节点 1. 什么是链表 链表是 ...

  8. Selenium3自动化测试【29】文件上传

    日常在访问页面时,文件上传与下载操作也常常用到,因此在Web自动化测试中也会遇到文件上传的情况.针对上传功能,WebDriver并没有提供对应的方法.针对上传文件的场景主要有两种解决思路: 同步视频知 ...

  9. Hyperledger Fabric 2.2 学习笔记:测试网络test-network

    写在前面 最近被Hyperledger Fabric折磨,归根结底还是因为自己太菜了qwq.学习路漫漫,笔记不能少.下面的步骤均是基于已经成功搭建了Fabric2.2环境,并且拉取fabric-sam ...

  10. C#语法糖系列 —— 第三篇:聊聊闭包的底层玩法

    有朋友好奇为什么将 闭包 归于语法糖,这里简单声明下,C# 中的所有闭包最终都会归结于 类 和 方法,为什么这么说,因为 C# 的基因就已经决定了,如果大家了解 CLR 的话应该知道, C#中的类最终 ...