mapper.xml文件解析由XMLConfigBuilder类开始,代码如下:
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 {
//从mapperconfig文件中读取mapperxml文件信息
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);
//开始解析mapperxml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//开始解析 见2.1、XMLMapperBuilder#pare
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.");
}
}
}
}
}
2、XMLMapperBuilder 2.1、XMLMapperBuilder#pareparent:mapper.config.xml文件件被解析后封装到XNode中。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析mapper标签,SQL的解析入口在这儿见2.1、XMLMapperBuilder#configurationElement
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//绑定命名空间
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
2.1、XMLMapperBuilder#configurationElement
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"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
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. Cause: " + e, e);
}
}
获取select|insert|update|delete等标签信息为一个list集合,最终会被封装到XMLStatementBuilder中进行解析并且每个配置文件都会被最终解析为一个MappedStatement。详见3、MappedStatement
private void buildStatementFromContext(List3、MappedStatement 3.1、MappedStatement#parseStatementNodelist, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
public void parseStatementNode() {
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse the SQL (pre: and were parsed and removed)
//此处会正真开始进行SQL的解析。其中context为XNode,即2.1中select等标签被解析后的XNode
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//....
}
langDriver:我认为就是一个解析器。默认为XMLLanguageDriver,另一种为RawLanguageDriver。本章我们以XMLLanguageDriver为例子进行讲解。
langDriver.createSqlSource内部使用XMLscriptBuilder进行解析XNode。最终将解析后的sqlSource保存到了mapperstatement中,并且在程序运行中会被调用。
public class XMLLanguageDriver implements LanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class> parameterType) {
XMLscriptBuilder builder = new XMLscriptBuilder(configuration, script, parameterType);
return builder.parsescriptNode();
}
}
4、XMLscriptBuilder 4.1、XMLscriptBuilder#parsescriptNodeconfiguration:全局配置,每个配置文件一个
script:mappxml文件解析后会被封装到XNode中
首先通过parseDynamicTags会对XNode的context解析为最小单元的SqlNode。解析结果会最终确认是否为动态SQL(动态SQL指标签类型为element类型的标签)
public SqlSource parsescriptNode() {
//对XNode进行解析为最小单元的SqlNode,解析结果会最终确认是否为动态SQL(动态SQL指标签类型为element类型的标签),详见4.2XMLscriptBuilder#parseDynamicTags
List contents = parseDynamicTags(context);
//最终被封装到MixedSqlNode中
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
//由于我们当前SQL是动态SQL所以最终会被封装到此类中。
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
4.2、XMLscriptBuilder#parseDynamicTags
XNode解析,解析结果会最终确认是否为动态SQL(动态SQL指标签类型为element类型的标签)
ListparseDynamicTags(XNode node) { List contents = new ArrayList (); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); //判断node类型是否为文本类型等, 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)); } //Node类型为element类型比如 } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); //此方法内部提供了mybatis支持的所有动态标签,见4.3、XMLscriptBuilder#nodeHandlers NodeHandler handler = nodeHandlers(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); //设置为动态SQL isDynamic = true; } } return contents; }
以如下select为例进行讲解
此标签会被封装到Node中。而在Node中会分别为俩个子标签。
Child1:
select qty from TestDistrubute
为文本标签,被封装到了StaticTextSqlNode中,并加入到了List contents = new ArrayList();
Child2:
id=#{id}
因为存在element标签,所以为动态标签标签类型为if类型。最终使用ifHandler进行解析。见5.1、IfHandler#handleNode
最终解析完成后如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sm5Wvcwh-1633502224460)(/Users/yanjinying/work/文件/Dockument/笔记/Spring/mybatis源码/mapperXml解析.assets/截屏2021-10-03 下午6.45.33-3258127.png)]
从图中可以看出大致结构如下:
conents:
- 0: StaticTextSqlNode
text: select qty from TestDistrubute
- 1: IfSqlNode
test: id != null
contents:
- 1: whereSqlNode
prefix: WHERe
contents:
- 1: StaticTextSqlNode
text: select qty from TestDistrubute
从最终结构可以看出,一条SQL最终按照element标签为最小单元被解析后,存入list集合中。
4.3、XMLscriptBuilder#nodeHandlers此方法中提供了mybatis支持的所有动态标签
NodeHandler nodeHandlers(String nodeName) {
Map map = new HashMap();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
5、NodeHandler
5.1、IfHandler#handleNode
接4.2中child2标签的解析。
id=#{id}
if标签同样会被封装到一个XNode中,在XNode中同样会存在一个子标签,子标签如下:
id=#{id}
而where标签则又是一个动态标签,从4.3、XMLscriptBuilder#nodeHandlers中可以看出应该会被采用WhereHandler进行解析。
当4.2中child2被封装到XNode中时候会进入IfHandler#handleNode,首先被parseDynamicTags进行递归解析为SQLNode的集合。然后被封装到MixedSqlNode中,再次被封装到IfSqlNode,最后加入List targetContents集合中。
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List targetContents) {
//在进行递归解析。见4.2、XMLscriptBuilder#parseDynamicTags
List contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
//获取test的条件判断表达式。
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
parseDynamicTags(nodeToHandle);最终解析后的结果如下:
从截图可以看出,大致的if标签的结构如下:
contents:
- prefix: WHERe
contents:
- text: id=#{id}
当if的test的条件成立时则where标签就会被解析追加到SQL中。追加部分逻辑在6.1、IfSqlNode#apply
6、运行时SQL解析一条完整的SQL需要根据启动时初始化好的SQL以及运行时的入参才能最终确认我们需要执行的SQL。
6.1、入参的解析一条SQL的执行首先从MapperProxy#invoke开始。
MapperProxy#invoke调用MapperMethod#execute,并且将入参封装到了Map
之后在executor中会触发MappedStatement#getBoundSql,并最终调用DynamicSqlSource#getBoundSql
6.2、DynamicSqlSource#getBoundSqlpublic BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
//见6.4、MixedSqlNode#apply
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//解析占位符
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
//入参进行绑定
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry entry : context.getBindings().entrySet()) {
//将入参全部绑定到boundSql
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
6.4、MixedSqlNode#applyparameterObject:入参
configuration:全局配置
rootSqlNode:4.1、XMLscriptBuilder#parsescriptNode解析的结果。类型为MixedSqlNode
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
contents:4.2、XMLscriptBuilder#parseDynamicTags解析的结果,为所有element标签的解析后集合
具体结果为如下:
当遍历第一个后content后context中的sqlBuilder会变为 select qty from TestDistrubute
当遍历第二个时因为元素为ifSqlNode所以会进入IfSqlNode#apply,见6.3、IfSqlNode#apply
6.3、IfSqlNode#apply源码如下:
@Override
public boolean apply(DynamicContext context) {
//test为5.1、IfHandler#handleNode中的test值即 id !=null,context.getBindings()为入参,evaluator为表达式执行器,当执行结果为true时会进入contents.apply(context)
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
test为5.1、IfHandler#handleNode中的test值即 id !=null,context.getBindings()为入参,evaluator为表达式执行器,当执行结果为true时会进入contents.apply(context),contents为5.1、IfHandler#handleNode中的mixedSqlNode,所以此处又会进入MixedSqlNode#apply,解析where等条件的判断,见6.4、MixedSqlNode#apply。
6.4、WhereSqlNode#applypublic boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
boolean result = contents.apply(filteredDynamicContext);
filteredDynamicContext.applyAll();
return result;
}
contents:5.1、IfHandler#handleNode解析的where标签的内容。
最终解析完成后的SQL为:
select qty from TestDistrubute
WHERe id=#{id}



