回想在上一章中我们欠下的技术债:
nodeHandler
sqlNode
sqlSource
ParameterMapping
下面我们将一个个解决,首先我们先选择nodeHandler和sqlNode这个技术点,因为他们是连在一起的。
1. NodeHandler源码分析
首先我们先要回顾下之前是在哪里欠下的债,不能糊里糊涂的。
之前在构建XMLScriptBuilder的过程中的initNodeHandlerMap方法
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
这些标签在我们使用MyBatis的过程中经常会用到,不过这里我们还是结合官网帮我们做的归类来进行分析。
第一批是 :
if
choose (when, otherwise)
trim (where, set)
foreach
我们先看NodeHandler接口的方法:
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
有了这个底之后,我们接下来边进行分析,首先我们从if标签的handler入手:
private class IfHandler implements NodeHandler {
public IfHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
这里有个很冷门的知识,关于 // Prevent Synthetic Access的,具体可以推荐文档:
java合成方法
这里不做展开。我们关注点放在MyBatis本身。
OK,我们继续,在上面的IfHandler中,我们又引入了MixedSqlNode类,我们来看下源码:
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
至于SqlNode接口,我们也来回忆下:
public interface SqlNode {
boolean apply(DynamicContext context);
}
总结:这里的MixedSqlNode顾名思义就是混合节点,对于存储多个SqlNode,然后调用。
我们继续向下看,介绍完了MixedSqlNode,我们来看它是怎么出现的:
protected MixedSqlNode parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<>();
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标签,转为没一个个SqlNode,涉及到递归调用,硬看的话会比较复杂难理解,我换成一种人脑比较好理解的,用实例理解:
<select id="selectOddPostsInKeysList" resultType="org.apache.ibatis.domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="keys"
open="(" separator="," close=")">
<if test="index % 2 != 0">
#{item}
</if>
</foreach>
ORDER BY P.ID
</select>
解析:在这里,因为是递归的,所以我们先会解析<if>,在解析<foreach>最后解析<select>,不要跟着代码顺序走,很容易让你烦躁,失去耐心,其实理解其意便可。最主要还是理解解析的流程:
- 如果是单纯的SQL,内部没有其他任何标签了,那么我们会进入到第一个方法
- 如果内部还有标签,会把标签解析的信息放在contents中,一层套一层,最终contents会返回我们所有的标签解析信息。
解析完这些我们在回过头来看:
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
这里大概的意思是解析if标签下的内容和if标签中的test内容,放入到IfSqlNode中,所以又引出了IfSqlNode类。我们来看下源码:
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
private final String test;
private final SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
其中 new ExpressionEvaluator中使用了Ognl来进行验证,我不太会这部分内容,这里也不做展开。只需知道这里是用来判断的。更多的可以查看调试ExpressionEvaluatorTest:
//ExpressionEvaluatorTest
@Test
void shouldCompareStringsReturnTrue() {
boolean value = evaluator.evaluateBoolean("username == 'cbegin'", new Author(1, "cbegin", "******", "cbegin@apache.org", "N/A", Section.NEWS));
assertTrue(value);
}
这一部分关于IfSqlNode标签解析的过程很可能会把你绕晕,这里我还是推荐跟读调试源码来进行阅读。
下面是我们第二个标签:choose (when, otherwise)标签
private class ChooseHandler implements NodeHandler {
public ChooseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> whenSqlNodes = new ArrayList<>();
List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
targetContents.add(chooseSqlNode);
}
private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
List<XNode> children = chooseSqlNode.getChildren();
for (XNode child : children) {
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler instanceof IfHandler) {
handler.handleNode(child, ifSqlNodes);
} else if (handler instanceof OtherwiseHandler) {
handler.handleNode(child, defaultSqlNodes);
}
}
}
private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
SqlNode defaultSqlNode = null;
if (defaultSqlNodes.size() == 1) {
defaultSqlNode = defaultSqlNodes.get(0);
} else if (defaultSqlNodes.size() > 1) {
throw new BuilderException("Too many default (otherwise) elements in choose statement.");
}
return defaultSqlNode;
}
}
}
这一段的代码比较长,在handleWhenOtherwiseNodes主要是用来解析choose下的when和otherwise节点,但是我们看到在注册hander的时候,我们的when节点其实注册的是IfHandler,顾所以这里判断了IfHandler。
private class OtherwiseHandler implements NodeHandler {
public OtherwiseHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
targetContents.add(mixedSqlNode);
}
}
而getDefaultSqlNode方法是主要保证只有一个Otherwise标签。
最后封装进ChooseSqlNode:
public class ChooseSqlNode implements SqlNode {
private final SqlNode defaultSqlNode;
private final List<SqlNode> ifSqlNodes;
public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : ifSqlNodes) {
if (sqlNode.apply(context)) {
return true;
}
}
if (defaultSqlNode != null) {
defaultSqlNode.apply(context);
return true;
}
return false;
}
}
这里的逻辑也比较简单,大家自行阅读即可。
2.今日总结
今天我们分析了关于nodeHandler和sqlnode源码的分析和解析过程,会很绕,但是如果把握住重心调试几遍,应该还是没有问题的~~~
网友评论