MyBatis 源码之解析篇

mybatis 初始化流程
步骤:
1、通过 ClassLoader 类加载器读取某个路径的 xml 文件来获取 InputStream 流对象.
2、通过 SqlSessionFactoryBuilder 对象来解析流, 返回工厂
3、通过 SqlSessionFactory 工厂获取 SqlSession对象
4、通过 SqlSession 可以操作. (查询、删除、修改、添加)

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sessionFactory.openSession();
sqlSession.insert(".....");

我们通过 SqlSesseionFactoryBuilder 类来分析,它是如何通过 InputStream 来构建工厂的.

	public SqlSessionFactory build(InputStream inputStream) {
		return build(inputStream, null, null); //方法重载
	}
	
	public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            //创建XMLConfigBuilder对象
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            //封装Configuration对象成SqlSessionFactory
            return build(parser.parse());   //parser.parse()解析返回Configuration 对象
        } 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.
            }
        }
    }
    
    //通过Configuration 创建 SqlSessionFactory 工厂  (此方法相当于中转)
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

可以发现 XMLConfigBuilder 对象中的 parse() 方法的作用。主要是把 InputStream 解析成一个 Configuration 对象。然 Configuration 对象在 mybatis 中是非常重要的。它是 mybatis 全局唯一个配置对象。通过它可以获取到所有的配置及信息。咱先暂时不解读 Configuration 对象.

XMLConfigBuilder 对象解析, 先看其构造器:

	//调用的构造器(实例化 XPathParser)
    public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
    }
    
    //parser 为mybatis 解析器. 暂不分析
	private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }

	//解析开始的地方. 也就是我们开始解析配置文件的第一步.
	public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;  //标识解析已开始 
        parseConfiguration(parser.evalNode("/configuration"));   //解析<configuration>标签
        return configuration;
    }

	// 解析文件推荐先看下 mybatis 官方文档:  http://www.mybatis.org/mybatis-3/zh/getting-started.html
	private void parseConfiguration(XNode root) {
        try {
            //解析<properties>标签
            propertiesElement(root.evalNode("properties"));
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            //解析<typeAliases>标签  别名
            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
            // 在objectFactory和objectWrapperFactory第631期之后阅读它
            //解析environments节点, 如若为空配置默认的
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            typeHandlerElement(root.evalNode("typeHandlers"));
            //解析mappers 节点对应的映射文件
            //解析<mappers>标签 			** 解析映射文件 (我们重点解析这个节点下的, 有兴趣的话可以看下其它的解析步骤)
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
	//解析映射文件  如: TestDao.xml 
	private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                if ("package".equals(child.getName())) {  //直接解析包
                    String mapperPackage = child.getStringAttribute("name");
                    configuration.addMappers(mapperPackage);
                } else {
                    String resource = child.getStringAttribute("resource"); //获取到xml路径
                    String url = child.getStringAttribute("url");  //空
                    String mapperClass = child.getStringAttribute("class");  //空
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        //获取 mapper.xml 文件InputStream
                        InputStream inputStream = Resources.getResourceAsStream(resource);
                        // 映射文件对应一个XmlMapperBuilder 对象
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //
                        mapperParser.parse(); //解析映射文件
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        InputStream inputStream = Resources.getUrlAsStream(url);
                        XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url == null && mapperClass != null) {
                        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.");
                    }
                }
            }
        }
    }

发现在解析下的节点时, mybatis 会通过 XMLMapperBuilder 对象来解析. 在构造 XMLMapperBuilder 对象会创建一个 XPathParser 对象及一个 XMLMapperEntityResolver 对象来帮助解析. 这二个对象暂时不分析, 有兴趣的话可以去了解下。 继续看源码

	public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
		//创建了 XPathParser  与  XMLMapperEntityResolver 
    	this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments);
	}

	private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
	    super(configuration);
	    this.builderAssistant = new MapperBuilderAssistant(configuration, resource); //命名空间构建助手
	    this.parser = parser;
	    this.sqlFragments = sqlFragments;
	    this.resource = resource;
  	}

映射文件解析:
1、解析节点做对应的储存.
2、在配置中心 Configuration 储存该命名空间
3、解析未解决的语句及结果集对象映射

注意: 这里解析的顺序. 先解析parameterMap 节点在 resultMap 节点在 sql 节点然后在 select… 的节点0

	public void parse() {
		// 如果当前的映射文件未被加载, 就开始解析.  (resource xml的命名空间)
	    if (!configuration.isResourceLoaded(resource)) {
	      //配置mapper节点下的属性
	      configurationElement(parser.evalNode("/mapper"));
	      configuration.addLoadedResource(resource); //储存映射文件的命名空间
	      bindMapperForNamespace(); //绑定命名空间路径对应的 Class 对象
	    }
	
	    //解析未解决的语句及结果集对象映射
	    parsePendingResultMaps();
	    parsePendingCacheRefs();
	    parsePendingStatements();
  	}
	
	//继续解析节点
	private void configurationElement(XNode context) {
        try {
            //mapper.xml的命名空间路径
            String namespace = context.getStringAttribute("namespace");
            if (namespace == null || namespace.equals("")) {  // 不设置命名空间会抛异常
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
            builderAssistant.setCurrentNamespace(namespace); // 給构建器助手设置当前命名空间

            //解析<mapper>标签下的 <cache-ref>标签 ======>> 不经常用不分析
            cacheRefElement(context.evalNode("cache-ref")); 
            cacheElement(context.evalNode("cache"));
            
            //解析<parameterMap>节点
            parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            //解析<resultMap>节点
            resultMapElements(context.evalNodes("/mapper/resultMap"));
            //解析<sql>节点
            sqlElement(context.evalNodes("/mapper/sql"));
            //解析<select> <insert> <update> <delete> 节点
            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);
        }
    }

	//解析<select> <insert> <update> <delete> 节点
	private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
            buildStatementFromContext(list, configuration.getDatabaseId());
        }
        buildStatementFromContext(list, null);
    }

	//DataBaseId 在mybatis-config.xml文件中若未设置就为null
	private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
        	// 创建 XMLStatementBuilder 对象
            final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
            try {
                statementParser.parseStatementNode();// 分析语句节点
            } catch (IncompleteElementException e) {
                configuration.addIncompleteStatement(statementParser);
            }
        }
    }

XMLStatementBuilder 这个对象先不需要太过于的深入。理解成解析 select 或者 insert 或者 update 或者 delete 标签就会去创建一个对应的 XMLStatementBuilder 对象. 创建完处理其中的属性. 如:id、parameterMap、parameterType、resultMap、 resultType…等等。然后通过这些属性创建一个 MappedStatement 对象 并把 MappedStatement 加入到 Configuration 对象中 mappedStatements 属性储存.

XMLStatementBuilder 代码如下:

	private final MapperBuilderAssistant builderAssistant;  //命名空间助手
    private final XNode context;  //对应的节点
    private final String requiredDatabaseId;  //在 mybatis-config.xml 中未配置就为 null

	//解析语句节点
    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId"); //null

        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }

        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);  //是否刷新缓存, 如果为 select 标签不使用刷新
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);       //select 标签使用缓存
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);    //不使用结果集排序

        // 在解析之前包含片段
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());

        // 参数类型
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);

        String lang = context.getStringAttribute("lang");  // 语言, 一般不用
        LanguageDriver langDriver = getLanguageDriver(lang);  //为null, 使用默认语言驱动 XMLLanguageDriver 对象

        // 在include之后解析selectKey并删除它们
        processSelectKeyNodes(id, parameterTypeClass, langDriver);

        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;   // 方法名 + "!selectKey"
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); // currentNamespace + "." + keyStatementId
        if (configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
            // 如果不是 insert 节点和不使用生成主键 则 KeyGenerator 为 NoKeyGenerator
            // configuration.isUseGeneratedKeys() 默认 false
            keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }

        // XMLScriptBuilder  中 parseScriptNode() 方法创建
        // SqlSource ==>> DynamicSqlSource : RawSqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); //创建 SqlSource 对象
        // 获取节点中属性值
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        Integer fetchSize = context.getIntAttribute("fetchSize");  // 取长度
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String resultType = context.getStringAttribute("resultType");
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultSetType = context.getStringAttribute("resultSetType");
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        String resultSets = context.getStringAttribute("resultSets");

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

SqlSource 的创建过程分析下. 通过 XMLLanguageDriver 对象语言驱动创建 SqlSource, 而 XMLScriptBuilder 是通过是否动态创建不同的 SqlSource 对象, 如 DynamicSqlSource 对象创建需要SqlNode 对象构成.
SqlSource 接口作用:通过传入的参数对象获取到 BoundSql 语句对象
SqlNode 接口作用: 就是解析sql文本时封装的对象。 如: select * from table ⇒ TextSqlNode 如:where标签 ⇒ WhereSqlNode 如:if标签 ⇒ IfSqlNode

/**
 * 封装的sql语句
 */
public class BoundSql {

    private final String sql; //sql语句
    private final List<ParameterMapping> parameterMappings; //定义的parameterMap 对象
    private final Object parameterObject;   //传入的参数对象
    private final Map<String, Object> additionalParameters; //附加参数 test[0].item[0].name
    private final MetaObject metaParameters;

    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap<>();
        this.metaParameters = configuration.newMetaObject(additionalParameters);
    }

    public String getSql() {
        return sql;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public Object getParameterObject() {
        return parameterObject;
    }

    //是否含附加参数名
    public boolean hasAdditionalParameter(String name) {
        String paramName = new PropertyTokenizer(name).getName();
        return additionalParameters.containsKey(paramName);
    }

    public void setAdditionalParameter(String name, Object value) {
        metaParameters.setValue(name, value);
    }

    public Object getAdditionalParameter(String name) {
        return metaParameters.getValue(name);
    }
}

大概思路:就是解析啥标签都会有对应的类来封装,如果来处理。 处理完统一保存到配置中心 Configuration 对象中。

上面提到的解析顺序, 先是parameterMap 在 resultMap 在 sql 标签最后时才是 insert / select / update / delete 标签这种。

我们想象一下, 解析 parameterMap 标签也有个对应的 ParameterMap 对象。 这个对象有对应的id及对应的type属性, 还有很parameter 元素标签, 就会有一个 ParameterMapping 对应的集合储存。就不贴代码了感兴趣可以去查看

ResultMap ==>> ResultMapping

	//解析<resultMap>标签
	private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        String type = resultMapNode.getStringAttribute("type",
                resultMapNode.getStringAttribute("ofType",
                        resultMapNode.getStringAttribute("resultType",
                                resultMapNode.getStringAttribute("javaType"))));
        Class<?> typeClass = resolveClass(type);
        if (typeClass == null) {
            typeClass = inheritEnclosingType(resultMapNode, enclosingType);
        }
        Discriminator discriminator = null;
        List<ResultMapping> resultMappings = new ArrayList<>();
        resultMappings.addAll(additionalResultMappings);
        List<XNode> resultChildren = resultMapNode.getChildren(); // 获取所有子节点 result
        for (XNode resultChild : resultChildren) {
            if ("constructor".equals(resultChild.getName())) { // constructor 标签
                processConstructorElement(resultChild, typeClass, resultMappings);
            } else if ("discriminator".equals(resultChild.getName())) {
                discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
            } else {
                List<ResultFlag> flags = new ArrayList<>();
                if ("id".equals(resultChild.getName())) {
                    flags.add(ResultFlag.ID);
                }
                // 构建 ResultMapping 对象(处理完所有的 result节点)
                resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); //构建 ResultMapping 对象
            }
        }
        String id = resultMapNode.getStringAttribute("id",
                resultMapNode.getValueBasedIdentifier());
        String extend = resultMapNode.getStringAttribute("extends"); // null
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // null
        // 分解器
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
            // 创建 ResultMap 并添加到 Configuration
            return resultMapResolver.resolve();
        } catch (IncompleteElementException e) {
            configuration.addIncompleteResultMap(resultMapResolver);
            throw e;
        }
    }

sql节点处理不同, 就是把id 处理成命名空间+"."+id 格式 如: com.pazz.dao.TestDao.testSqlId 及解析的Node对象储存给 XMLMapperBuilder 中的 Map<String, XNode> sqlFragments 属性保存.
代码不贴自行看代码

mybatis 解析篇暂时大概就这样了… 其中 Configuration 为核心 ,通过Configuration 对象构建 SqlSessionFactory 工厂对象

SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(Configuration);

下篇为大家带来mybatis 核心逻辑处理篇…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值