Mybatis源码分析之Mapper文件解析
2018-03-01 21:41
645 查看
感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火!
xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析:
configurationElement(parser.evalNode(“/mapper”));
上面的这行代码是提取部分来解析:
将各个元素细分,逐一解析:
- parameterMapElement方法处理parameterMap节点部分
- resultMapElements方法处理resultMap节点部分
- sqlElement处理sql节点部分
- buildStatementFromContext方法处理select|insert|update|delete部分
重点看看buildStatementFromContext:
List类型的list包含了单个mapper.xml文件的所有sql动作部分:
单一节点使用XMLStatementBuilder的parseStatementNode来解析,取其中重要的三行代码:
List contents = parseDynamicTags(context);
if块是处理text部分,else块处理其他内嵌node部分:
最终的结果都会添加到List类型的contexts中。XNode形如父子关系,类似链表存储。
例如:
body部分:
取上面的text构成TextSqlNode
第二个childNode是if标签包裹部分,取出来的body为:
NodeHandler handler = nodeHandlers.get(nodeName);
上面的代码获取IfHandler(对应的还有ChooseHandler,ForEachHandler等)。
handler.handleNode(child, contents);
看看内部类IfHandler会如何处理:
继续调用parseDynamicTags,然后构造IfSqlNode,添加到总的contents中。
这时候name为“select”的XNode下解析出的contents包含了三个SqlNode:
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
利用contents构造MixedSqlNode类型的rootSqlNode。
SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
利用rootSqlNode构造DynamicSqlSource。DynamicSqlSource的getBoundSql方法是非常重要的,用来获取BoundSql。此时仅仅是构造sqlSource放到MappedStatement中,以sqlSource形式入,以BoundSql形式出。
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, keyGenerator, keyProperty, keyColumn, databaseId);
解析完一个statement节点,就会将其包装成MappedStatement,基本上就是你在Mapper.xml文件中写的每个sql语句对应一个MappedStatement。最终都添加到Configuration的MappedStatement集合中。
rootSqlNode.apply(context);
我们之前存的rootSqlNode是一个MixedSqlNode,代表混合型SqlNode,看其apply方法:
就是将之前的SqlNode集合contents遍历处理。这个contents包含两种类型的SqlNode:TextSqlNode和IfSqlNode。
TextSqlNode的apply方法:
这里就涉及到参数绑定了,将${param}替换为实际参数值。
IfSqlNode的apply方法:
通常IfSqlNode也是包含一个TextSqlNode,表达式满足要求就继续调用TextSqlNode的apply方法,append满足条件的sql语句。
这样一个动态sql就构造出来了个大概。后面还有进一步的处理:
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);
主要是针对#{param}部分的处理,后面在”参数绑定“分析时会详细解读。
xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析:
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); }
configurationElement(parser.evalNode(“/mapper”));
上面的这行代码是提取部分来解析:
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("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")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e); } }
将各个元素细分,逐一解析:
- parameterMapElement方法处理parameterMap节点部分
- resultMapElements方法处理resultMap节点部分
- sqlElement处理sql节点部分
- buildStatementFromContext方法处理select|insert|update|delete部分
重点看看buildStatementFromContext:
private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } 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); } } }
List类型的list包含了单个mapper.xml文件的所有sql动作部分:
<select></select> <insert></insert> <update></update> <delete></delete>
单一节点使用XMLStatementBuilder的parseStatementNode来解析,取其中重要的三行代码:
List<SqlNode> contents = parseDynamicTags(context); MixedSqlNode rootSqlNode = new MixedSqlNode(contents); SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
List contents = parseDynamicTags(context);
private List<SqlNode> 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)); String nodeName = child.getNode().getNodeName(); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); contents.add(new TextSqlNode(data)); } else { NodeHandler handler = nodeHandlers.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); } } return contents; }
if块是处理text部分,else块处理其他内嵌node部分:
<if></if> <choose></choose> ....
最终的结果都会添加到List类型的contexts中。XNode形如父子关系,类似链表存储。
例如:
<select id="getCurrSpaceNums" resultType="com.fcs.model.CarParkingSpaceNum"> select pp.permit_cards maxLeng,pp.permit_cards currLeng from TB_UHOME_PARKING_PLACE pp where pp.COMMUNITY_ID=#{orgId} and pp.STATUS='1' and pp.PLACE_CODE = #{parkingCode} <if test="parkingArea != null and parkingArea !=''"> and pp.PLACE_AREA= #{parkingArea} </if> </select>
body部分:
select pp.permit_cards maxLeng,pp.permit_cards currLeng from TB_UHOME_PARKING_PLACE pp where pp.COMMUNITY_ID=#{orgId} and pp.STATUS='1' and pp.PLACE_CODE = #{parkingCode}
取上面的text构成TextSqlNode
第二个childNode是if标签包裹部分,取出来的body为:
and pp.PLACE_AREA= #{parkingArea}
NodeHandler handler = nodeHandlers.get(nodeName);
上面的代码获取IfHandler(对应的还有ChooseHandler,ForEachHandler等)。
handler.handleNode(child, contents);
看看内部类IfHandler会如何处理:
private class IfHandler implements NodeHandler { public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { List<SqlNode> contents = parseDynamicTags(nodeToHandle); MixedSqlNode mixedSqlNode = new MixedSqlNode(contents); String test = nodeToHandle.getStringAttribute("test"); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } }
继续调用parseDynamicTags,然后构造IfSqlNode,添加到总的contents中。
这时候name为“select”的XNode下解析出的contents包含了三个SqlNode:
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
利用contents构造MixedSqlNode类型的rootSqlNode。
SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
利用rootSqlNode构造DynamicSqlSource。DynamicSqlSource的getBoundSql方法是非常重要的,用来获取BoundSql。此时仅仅是构造sqlSource放到MappedStatement中,以sqlSource形式入,以BoundSql形式出。
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, keyGenerator, keyProperty, keyColumn, databaseId);
解析完一个statement节点,就会将其包装成MappedStatement,基本上就是你在Mapper.xml文件中写的每个sql语句对应一个MappedStatement。最终都添加到Configuration的MappedStatement集合中。
补充
在DynamicSqlSource的getBoundSql方法中有下面一行代码:rootSqlNode.apply(context);
我们之前存的rootSqlNode是一个MixedSqlNode,代表混合型SqlNode,看其apply方法:
public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; }
就是将之前的SqlNode集合contents遍历处理。这个contents包含两种类型的SqlNode:TextSqlNode和IfSqlNode。
TextSqlNode的apply方法:
public boolean apply(DynamicContext context) { GenericTokenParser parser = new GenericTokenParser("${", "}", new BindingTokenParser(context)); context.appendSql(parser.parse(text)); return true; }
这里就涉及到参数绑定了,将${param}替换为实际参数值。
IfSqlNode的apply方法:
public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; }
通常IfSqlNode也是包含一个TextSqlNode,表达式满足要求就继续调用TextSqlNode的apply方法,append满足条件的sql语句。
这样一个动态sql就构造出来了个大概。后面还有进一步的处理:
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);
主要是针对#{param}部分的处理,后面在”参数绑定“分析时会详细解读。
相关文章推荐
- mybatis源码学习之执行过程分析(2)——config.xml配置文件和mapper.xml映射文件解析过程
- Mybatis3源码分析(三):解析mapper的xml配置文件
- Mybatis工作机制源码分析—初始化—mapper配置文件解析
- Mybatis3源码分析(三):解析mapper的xml配置文件
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
- MyBatis源码分析(1)-MapConfig文件的解析
- MyBatis源码分析:如何解析配置文件
- Mybatis架构设计及源码分析-mapper.xml文件解析
- MyBatis官方教程及源码解析——mapper映射文件
- mybatis源码分析5 - mapper读写数据库完全解析
- Mybatis 源码解析三、Mapper接口与mapper.xml文件绑定
- MyBatis-3.4.2-源码分析14:XML解析之sqlElement(context.evalNodes("/mapper/sql"))
- MyBatis源码(四)之mapper文件解析和动态Sql解析启动阶段
- MyBatis-3.4.2-源码分析17:XML解析之bindMapperForNamespace
- 【mybatis源码分析】原理分析之三:初始化(配置文件读取和解析)
- MyBatis官方教程及源码解析——mapper映射文件
- mybatis源码-解析配置文件(四)之配置文件Mapper解析
- mybatis底层源码分析之--配置文件读取和解析
- MyBatis 源码分析 - 映射文件解析过程
- MyBatis 源码分析 - 配置文件解析过程