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

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

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

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

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

启用二级缓存

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

    
        
    
  • 在需要使用二级缓存的Mapper配置文件中配置  标签

  • 在具体CRUD标签上配置useCache = true

标签  解析

//调用的重载方法
    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解析器对象,读取节点内数据,是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
            // 解析  标签
            propertiesElement(root.evalNode("properties"));
            // 解析  标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析  标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析  标签
            pluginElement(root.evalNode("plugins"));
            // 解析  标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析  标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析  标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值  到 Configuration 属性
            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);
        }
    }
​
--> mapperElement()
    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            // 遍历子节点
            for (XNode child : parent.getChildren()) {
                // 如果是 package 标签,则扫描该包
                if ("package".equals(child.getName())) {
                    // 获取  节点中的 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)) {
            // 解析 `` 节点
            configurationElement(parser.evalNode("/mapper"));
            // 标记该 Mapper 已经加载过
            configuration.addLoadedResource(resource);
            // 绑定 Mapper
            bindMapperForNamespace();
        }
​
        // 解析待定的  节点
        parsePendingResultMaps();
        // 解析待定的  节点
        parsePendingCacheRefs();
        // 解析待定的 SQL 语句的节点
        parsePendingStatements();
    }
​
--> configurationElement()
    // 解析 `` 节点
    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);
            // 解析  节点
            cacheRefElement(context.evalNode("cache-ref"));
            // 解析  节点
            cacheElement(context.evalNode("cache"));
            // 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            // 解析  节点们
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            // 解析  节点们
            sqlElement(context.evalNodes("/mapper/sql"));
            // 解析     节点们
    private void buildStatementFromContext(List list) {
        if (configuration.getDatabaseId() != null) {
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
        // 上面两块代码,可以简写成 buildStatementFromContext(list, configuration.getDatabaseId());
    }
    
    private void buildStatementFromContext(List list, String requiredDatabaseId) {
        //遍历