在 XMLStatementBuilder 当中解析的时候,有一步是获取 SqlSource,内部使用的是 XMLScriptBuilder 来获取。这篇主要来分析跟 SqlSource 有关的几个类。

SqlNode

先来回顾下 XMLScriptBuilder 的解析源代码:

1
2
3
4
5
6
7
8
9
10
11
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}

上一篇文章已经详细的分析了 parseDynamicTags 的解析过程,先看一下 SqlNode 接口的定义:

1
2
3
public interface SqlNode {
boolean apply(DynamicContext context);
}

接口非常简单。再来简单看下 parseDynamicTags 方法的解析过程:

  1. 是文本的创建 TextSqlNode 或者 StaticTextSqlNode
  2. 是元素的由 NodeHandler 来生成 SqlNode

先看 NodeHandler 接口的定义,它是 XMLScriptBuilder 的内部接口:

1
2
3
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

再来看看有哪些 NodeHandler 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
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);
}

具体对应的 XML 简单如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<select id="select" parameterType="map">
<bind name="" value=""/>
Select *
From user
<trim>
<where>
<if test="id != null">
id = #{id}
</if>
<choose>
<when test=""></when>
<otherwise></otherwise>
</choose>
</where>
</trim>
</select>
<update id="">
update xxx
<set>
</set>
</update>
<insert id="">
insert XX
<foreach collection="">
</foreach>
</insert>

简单的看 WhereHandler 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
private class WhereHandler implements NodeHandler {
public WhereHandler() {
// Prevent Synthetic Access
}

@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}

handleNode 方法主要是递归的调用 parseDynamicTags 来解析嵌套的元素,然后创建一个 MixedSqlNode 实例,最后在创建 WhereSqlNode 实例,并且添加到 targetContents 当中。

因为在 parseDynamicTags 方法当中,最后需要返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
//省略代码
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
//省略代码
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}

用一个例子,再来详细的体会整个过程:

1
2
3
4
5
6
7
8
  <select id="select" parameterType="map">
Select * From user
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</select>

第一次进入 parseDynamicTags 传递的 node 是整个 select 的 node。

然后循环它的所有子节点,第一个节点是字符串元素,并且不是动态的所以创建了一个 StaticTextSqlNode 实例,并且加入到了 contents 当中。

第二次循环是 where 节点,然后根据分析会递归调用 parseDynamicTags 方法,然后获取它的子节点是 if 节点,然后继续调用 IfHandler 的 handleNode 方法,同样也是递归调用 parseDynamicTags。

if 节点里面是字符串文本,所以返回就是 StaticTextSqlNode 实例。

每次递归的时候,都会把 NodeHandler 创建的 SqlNode 实例,添加到传递进来的 targetContents 当中。

最后返回的时候 contents 当中有两个元素,第一个是 Select 的字符串文本 StaticTextSqlNode,另外的 WhereSqlNode,然后 WhereSqlNode 的里面包含了 IfSqlNode, IfSqlNode 里面包含了 StaticTextSqlNode。

MixedSqlNode 特殊的 SqlNode

一个比较特殊的 SqlNode 是 MixedSqlNode,因为在大多数的 Hadnler 当中都会有如下的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);

public class MixedSqlNode implements SqlNode {
private List<SqlNode> contents;

public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}

@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
}

使用了组合模式的方式,把当前节点的所有子节点嵌套了起来,因为例如 WhereSqlNode 当中的属性是如下这样:

1
private SqlNode contents;

而 MixedSqlNode 当中是一个 List。

下篇继续分析

—EOF—