Mybatis架构设计及源码分析-mapper.xml文件解析
2018-05-26 23:58
721 查看
使用mybatis我们通常会自己去书写mapper.xml文件,文件内部通常会书写sql语句相关信息,下面我们将分析mybatis是如何解析这些信息并保存于全部配置类Configuration中。
回到之前的代码mapperElement(root.evalNode("mappers"));会解析mappers下面的节点信息。mybatis有四种方式配置mappers下面节点信息分别是:
第一和第二种很现实是直接配置的mapper.xml文地址。第三和第四种是配置的接口和包级别。具体解析过程如下:
分别遍历子节点:
我们先分析第一种和第二种情况:显然第一种和第二种情况执行的是类似的代码只是路径的标识方式不同而已,我们就取rresource表示来分析,
上述代码通过读取配置的xml文件并构造XMLMapperBuilder来解析xml文件,具体为:
首先判断xml资源是否被加载过了,默认肯定是没有加载过,么有加载过接着解析mapper节点例如:
解析代码如下:
这段代码的逻辑就是解析mapper.xml文件中的节点信息。我们具体看下解析CURD节点信息,CURD每个节点都会被解析成一个MappedStatement对象。
这段代码构建了XMLStatementBuilder,我们很多mapper映射文件都是以xml文件形式存在,重点看下parseStatementNode,其实现如下:
上面这个段代码基本就是解析CURD节点的属性并构建一个MappedStatement对象,各种属性可以查阅mybatis官方文档(http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html)。这里重点介绍下SqlSource构建过程:
SqlSource通过传入配置信息,节点信息以及参数类型创建,有了SqlSource就能拿到具体执行的sql语句信息,createSqlSource的具体实现在XMLLanguageDriver中:
parseScriptNode方法具体执行了解析过程:
parseDynamicTags这句代码的主要工程是解析动态sql语句标签,其实现如下:
比如我们的sql语句映射如下:
这里我们暂时不考虑动态sql,那么parseDynamicTags基本不会执行直接跳到最后返回,这样得到的SqlSource为RawSqlSource。后续只要调用SqlSource的getBoundSql就能得到解析的sql语句以及参数信息。这样mapper映射xml文件基本解析完毕并存储于Configuration对象中。
再次回到XMLMapperBuilder中的parase()方法中,
解析完mapper节点后会标记mapper节点已经解析好了,bindMapperForNamespace将Mapper接口与当前命名空间绑定起来,也就是将Mapper接口类与对应的xml文件绑定起来,通常namespace是Mapper接口的全路径名,这样方便后续直接通过mapper接口获取解析后的xml文件信息。
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
分别为从configuration中移除未完成的ResultMaps,CacheRefs以及Statements。这三个东西都是在前面 configurationElement(parser.evalNode("/mapper"));解析是发生失败后后会添加到一个未完成的状态中,例如:
随后会对这部分进行重新二次解析并移除。
到此为止第一二种配置基本解析完成,第三四种类似只是第三四中只不过采用@annnotation方式将sql语句直接写入到Mapper接口上面。
回到之前的代码mapperElement(root.evalNode("mappers"));会解析mappers下面的节点信息。mybatis有四种方式配置mappers下面节点信息分别是:
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers> <!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers> <!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers> <!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
第一和第二种很现实是直接配置的mapper.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"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); 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) { 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."); } } } } }
分别遍历子节点:
我们先分析第一种和第二种情况:显然第一种和第二种情况执行的是类似的代码只是路径的标识方式不同而已,我们就取rresource表示来分析,
ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse();
上述代码通过读取配置的xml文件并构造XMLMapperBuilder来解析xml文件,具体为:
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
首先判断xml资源是否被加载过了,默认肯定是没有加载过,么有加载过接着解析mapper节点例如:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.mybatis.example.BlogMapper"> <select id="selectBlog" resultType="Blog"> select * from Blog where id = #{id} </select> </mapper>
解析代码如下:
private void configurationElement(XNode context) { try { //解析命名空间 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); //解析缓存参照节点信息 cacheRefElement(context.evalNode("cache-ref")); //解析二级缓存信息 cacheElement(context.evalNode("cache")); //被废除信息 parameterMapElement(context.evalNodes("/mapper/parameterMap")); //解析resultMap节点 resultMapElements(context.evalNodes("/mapper/resultMap")); //节气sql语句节点 sqlElement(context.evalNodes("/mapper/sql")); //解析curd语句 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); } }
这段代码的逻辑就是解析mapper.xml文件中的节点信息。我们具体看下解析CURD节点信息,CURD每个节点都会被解析成一个MappedStatement对象。
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
这段代码构建了XMLStatementBuilder,我们很多mapper映射文件都是以xml文件形式存在,重点看下parseStatementNode,其实现如下:
public void parseStatementNode() { String id = context.getStringAttribute("id"); 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"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 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 includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
上面这个段代码基本就是解析CURD节点的属性并构建一个MappedStatement对象,各种属性可以查阅mybatis官方文档(http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html)。这里重点介绍下SqlSource构建过程:
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
SqlSource通过传入配置信息,节点信息以及参数类型创建,有了SqlSource就能拿到具体执行的sql语句信息,createSqlSource的具体实现在XMLLanguageDriver中:
public class XMLLanguageDriver implements LanguageDriver { @Override public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); } @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { //构建XMLScriptBuilder XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); //解析节点 return builder.parseScriptNode(); } @Override public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) { // issue #3 if (script.startsWith("<script>")) { XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver()); return createSqlSource(configuration, parser.evalNode("/script"), parameterType); } else { // issue #127 script = PropertyParser.parse(script, configuration.getVariables()); TextSqlNode textSqlNode = new TextSqlNode(script); if (textSqlNode.isDynamic()) { return new DynamicSqlSource(configuration, textSqlNode); } else { return new RawSqlSource(configuration, script, parameterType); } } } }
parseScriptNode方法具体执行了解析过程:
public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
parseDynamicTags这句代码的主要工程是解析动态sql语句标签,其实现如下:
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<SqlNode>(); NodeList children = node.getNode().getChildNodes(); //遍历每一个子节点 for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); }
比如我们的sql语句映射如下:
<select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = ‘ACTIVE’ </select>
这里我们暂时不考虑动态sql,那么parseDynamicTags基本不会执行直接跳到最后返回,这样得到的SqlSource为RawSqlSource。后续只要调用SqlSource的getBoundSql就能得到解析的sql语句以及参数信息。这样mapper映射xml文件基本解析完毕并存储于Configuration对象中。
再次回到XMLMapperBuilder中的parase()方法中,
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
解析完mapper节点后会标记mapper节点已经解析好了,bindMapperForNamespace将Mapper接口与当前命名空间绑定起来,也就是将Mapper接口类与对应的xml文件绑定起来,通常namespace是Mapper接口的全路径名,这样方便后续直接通过mapper接口获取解析后的xml文件信息。
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
分别为从configuration中移除未完成的ResultMaps,CacheRefs以及Statements。这三个东西都是在前面 configurationElement(parser.evalNode("/mapper"));解析是发生失败后后会添加到一个未完成的状态中,例如:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
随后会对这部分进行重新二次解析并移除。
到此为止第一二种配置基本解析完成,第三四种类似只是第三四中只不过采用@annnotation方式将sql语句直接写入到Mapper接口上面。
相关文章推荐
- 有关SpringMVC的架构设计、源码解析、得失分析
- MyBatis-3.4.2-源码分析18:XML解析之RoleMapper userMapper = sqlSession.getMapper(RoleMapper.class)
- mybatis源码学习之执行过程分析(2)——config.xml配置文件和mapper.xml映射文件解析过程
- Mybatis工作机制源码分析—初始化—mapper配置文件解析
- Mybatis架构设计及源码分析-Mybatis配置文件初始化全过程
- (六)Tomcat源码解析 - Tomcat 系统架构与设计模式(二)-设计模式分析
- Mybatis源码分析之Mapper文件解析
- MyBatis-3.4.2-源码分析14:XML解析之sqlElement(context.evalNodes("/mapper/sql"))
- Mybatis架构设计及源码分析-一条语句执行的过程
- MyBatis-3.4.2-源码分析12:XML解析之mapperElement(root.evalNode("mappers"))
- mybatis源码分析5 - mapper读写数据库完全解析
- MyBatis-3.4.2-源码分析17:XML解析之bindMapperForNamespace
- Mybatis架构设计及源码分析-SqlSessionFactory
- Mybatis架构设计及源码分析-SqlSession
- Mybatis3源码分析(三):解析mapper的xml配置文件
- Mybatis3源码分析(三):解析mapper的xml配置文件
- Mybatis架构设计及源码分析-MappedStatement
- MyBatis架构设计及源代码分析系列(一):MyBatis架构
- 【MyBatis源码分析】objectFactory解析属性配置元素详述
- Hadoop架构设计与源码分析视频教程