回想在上一章中我們欠下的技術(shù)債:
nodeHandler
sqlNode
sqlSource
ParameterMapping
下面我們將一個(gè)個(gè)解決,首先我們先選擇nodeHandler和sqlNode這個(gè)技術(shù)點(diǎn)猛蔽,因?yàn)樗麄兪沁B在一起的。
1. NodeHandler源碼分析
首先我們先要回顧下之前是在哪里欠下的債,不能糊里糊涂的。
之前在構(gòu)建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());
}
這些標(biāo)簽在我們使用MyBatis的過程中經(jīng)常會(huì)用到症革,不過這里我們還是結(jié)合官網(wǎng)幫我們做的歸類來進(jìn)行分析。
第一批是 :
if
choose (when, otherwise)
trim (where, set)
foreach
我們先看NodeHandler接口的方法:
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}
有了這個(gè)底之后鸯旁,我們接下來邊進(jìn)行分析噪矛,首先我們從if標(biāo)簽的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);
}
}
這里有個(gè)很冷門的知識(shí),關(guān)于 // Prevent Synthetic Access的铺罢,具體可以推薦文檔:
java合成方法
這里不做展開艇挨。我們關(guān)注點(diǎn)放在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);
}
總結(jié):這里的MixedSqlNode顧名思義就是混合節(jié)點(diǎn)蛋勺,對(duì)于存儲(chǔ)多個(gè)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標(biāo)簽,轉(zhuǎn)為沒一個(gè)個(gè)SqlNode碉怔,涉及到遞歸調(diào)用烘贴,硬看的話會(huì)比較復(fù)雜難理解,我換成一種人腦比較好理解的撮胧,用實(shí)例理解:
<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>
解析:在這里桨踪,因?yàn)槭沁f歸的,所以我們先會(huì)解析<if>,在解析<foreach>最后解析<select>,不要跟著代碼順序走芹啥,很容易讓你煩躁锻离,失去耐心,其實(shí)理解其意便可墓怀。最主要還是理解解析的流程:
- 如果是單純的SQL汽纠,內(nèi)部沒有其他任何標(biāo)簽了,那么我們會(huì)進(jìn)入到第一個(gè)方法
- 如果內(nèi)部還有標(biāo)簽傀履,會(huì)把標(biāo)簽解析的信息放在contents中虱朵,一層套一層,最終contents會(huì)返回我們所有的標(biāo)簽解析信息啤呼。
解析完這些我們?cè)诨剡^頭來看:
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
這里大概的意思是解析if標(biāo)簽下的內(nèi)容和if標(biāo)簽中的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來進(jìn)行驗(yàn)證翅敌,我不太會(huì)這部分內(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);
}
這一部分關(guān)于IfSqlNode標(biāo)簽解析的過程很可能會(huì)把你繞暈,這里我還是推薦跟讀調(diào)試源碼來進(jìn)行閱讀卖陵。
下面是我們第二個(gè)標(biāo)簽:choose (when, otherwise)標(biāo)簽
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é)點(diǎn),但是我們看到在注冊(cè)hander的時(shí)候泪蔫,我們的when節(jié)點(diǎn)其實(shí)注冊(cè)的是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方法是主要保證只有一個(gè)Otherwise標(biāo)簽撩荣。
最后封裝進(jìn)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;
}
}
這里的邏輯也比較簡(jiǎn)單铣揉,大家自行閱讀即可。
2.今日總結(jié)
今天我們分析了關(guān)于nodeHandler和sqlnode源碼的分析和解析過程餐曹,會(huì)很繞逛拱,但是如果把握住重心調(diào)試幾遍,應(yīng)該還是沒有問題的~~~