本文主要是介绍Mybatis源码分析(1)—— Mapper文件解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火!
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 ppwhere 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源码分析(1)—— Mapper文件解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!