回想在上一章中我們欠下的技術債:
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的過程中經(jīng)常會用到,不過這里我們還是結合官網(wǎng)幫我們做的歸類來進行分析。
第一批是 :
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,我們繼續(xù),在上面的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顧名思義就是混合節(jié)點,對于存儲多個SqlNode,然后調(diào)用。
我們繼續(xù)向下看,介紹完了MixedSqlNode,我們來看它是怎么出現(xiàn)的:
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,涉及到遞歸調(diào)用,硬看的話會比較復雜難理解,我換成一種人腦比較好理解的,用實例理解:
<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,內(nèi)部沒有其他任何標簽了,那么我們會進入到第一個方法
- 如果內(nèi)部還有標簽,會把標簽解析的信息放在contents中,一層套一層,最終contents會返回我們所有的標簽解析信息。
解析完這些我們在回過頭來看:
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
這里大概的意思是解析if標簽下的內(nèi)容和if標簽中的test內(nèi)容,放入到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來進行驗證,我不太會這部分內(nèi)容,這里也不做展開。只需知道這里是用來判斷的。更多的可以查看調(diào)試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標簽解析的過程很可能會把你繞暈,這里我還是推薦跟讀調(diào)試源碼來進行閱讀。
下面是我們第二個標簽: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節(jié)點,但是我們看到在注冊hander的時候,我們的when節(jié)點其實注冊的是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源碼的分析和解析過程,會很繞,但是如果把握住重心調(diào)試幾遍,應該還是沒有問題的~~~