在XMLLanguageDriver的createSQLSource()方法中,可以将已经经过解析的xml节点传入,并且传入相应的参数类型,开始动态sql语句的生成。
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
这里,根据每一个xml节点生成了新的XMLScriptBuilder,可以看到其构造方法。
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
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());
}
这里除了将所要解析的xml结构数据和参数类型赋值之后,初始化保存了Handler的map,可以看到里面的Handler,顾名思义,不同的xml标签的动态sql语句将交由这里的Handler进行解析。
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
protected MixedSqlNode 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) {
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);
}
在完成XMLScriptBuilder的生成之后,将会调用其parseScriptNode()方法开始对其进行解析。
在其中会直接通过parseDynamicTags()方法开始对xml解析后的Node开始具体解析成sqlNode。可以看到,首先会根据获取该节点下面的所有节点一次解析。首先会判断是否是text文本或者是CDATA类型的节点,那么会直接创建成TextSqlNode,其构造方法很简单,只是简单的把文本给赋值在TextSqlNode当中,之后调用isDynamic()方法来判断该节点是否是动态节点,实现如下。
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
这里首先会构建一个checker,checker主要来记录该TextSqlNode的是否动态的状态,之后将其作为参数作为createParser()方法的参数传入,这个方法构建了一个GenericTokenParser来做具体的动态评判,可以看到这里将中括号作为解析是否动态的符号。
最后通过parse()方法对具体的文本开始解析。
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
这里会寻找中括号的两边,如果能够找到,则 说明这里的TextSQLNode是动态的,则会在之前刚刚创建的checker里设置为动态。
在回到parseDynamic()方法,根据之前是否为动态,创建TextSQLNode或者StaticTextSQLNode加入在context中。然而,如果该xml的节点的类型如果为element,则说明还需要进一步的解析。在之前的构造方法中已经调用了init方法,在map里存放了各个动态sql语句里面支持的handler,将会在这里根据节点的name取得,如果取不到,说明采用了非法的名字,在这里会抛出异常,如果取到了,则会在这里通过handleNode()方法处理。
以常用的TrimHandler作为例子。
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
String prefix = nodeToHandle.getStringAttribute("prefix");
String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
String suffix = nodeToHandle.getStringAttribute("suffix");
String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
targetContents.add(trim);
}
在这里,首先会以这个trim节点作为根节点再一次调用parseDynamicTags()方法,进一步解析下面的子节点得到MixSqlNode。在解析完毕之后,设置相应的前后缀以及需要覆盖的前后缀。
最后,以以上得到的结果作为参数生成TrimSqlNode,其构造方法只是简单的按照上述赋值。而这个SqlNode就是经过xml解析之后得到的抽象sqlNode。
而SetHanler的实现也如下。
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
targetContents.add(set);
}
public class SetSqlNode extends TrimSqlNode {
private static List<String> suffixList = Arrays.asList(",");
public SetSqlNode(Configuration configuration,SqlNode contents) {
super(configuration, contents, "SET", null, null, suffixList);
}
}
SetSQLNode继承了TrimSQLNode,可以看到,实则其构造方法只调用了 TrimSQLNode的构造方法。只是强制把前缀设为了SET。WhereHandler的实现与set的实现也类似,实则也是trimSqlNode的实现,只是对前后缀进行了强制。
@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);
}
IfHandler则必须要将其中test中的表达式取出,赋值在其构造方法中,但是其构造方法只是简单的赋值,其并未继承自TrimSqlNode。
以上只是针对几种动态xml的标签的handler实现。在完成所有来自xml的node之后的解析抽象成具体的node之后,具体的sql生成的实现则需要在DynamicSqlSource中调用getBoundSql()方法中,调用根节点的apply方法。
以TextSQLNode的apply()为例子(静态StaticTextSQLNode的apply()方法只是简单的把静态字符串赋值)。
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
这里生成了一个BindingTokenParser,作为GenericTokenParser的Handler参数,与之前的是否动态的区别在于BindingTokenParser的handleToken()实现的更加详细。
@Override
public String handleToken(String content) {
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
checkInjection(srtValue);
return srtValue;
}
这里会从之前的上下文中根据前面parse()方法中得到的中括号之间的name来取得上下文中的参数注入,也就是这里完成了动态参数的注入。之后,与静态方法一样将注入好了的sql语句加在结果结果上下文中。
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
public boolean evaluateBoolean(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value instanceof Number) {
return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
}
return value != null;
}
而在IfSqlNode里,则会通过ognl来将之前从test中取得的表达式与传入的参数进行验证,如果验证通过,则会调用if节点下面的子节点的apply()完成if逻辑的表达。
@Override
public boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
boolean result = contents.apply(filteredDynamicContext);
filteredDynamicContext.applyAll();
return result;
}
而TrimSqlNode的apply方法则较为复杂,首先会根据传入的上下文生成FilterDynamicContext,并调用其下所有子节点的apply()方法,并将生成的sql结果存放在FilteredDynamicContext中。
public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { if (!prefixApplied) { prefixApplied = true; if (prefixesToOverride != null) { for (String toRemove : prefixesToOverride) { if (trimmedUppercaseSql.startsWith(toRemove)) { sql.delete(0, toRemove.trim().length()); break; } } } if (prefix != null) { sql.insert(0, " "); sql.insert(0, prefix); } } }
this.delegate = delegate; this.prefixApplied = false; this.suffixApplied = false; this.sqlBuffer = new StringBuilder();}public void applyAll() { sqlBuffer = new StringBuilder(sqlBuffer.toString().trim()); String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH); if (trimmedUppercaseSql.length() > 0) { applyPrefix(sqlBuffer, trimmedUppercaseSql); applySuffix(sqlBuffer, trimmedUppercaseSql); } delegate.appendSql(sqlBuffer.toString());}
最后调用applyAll()方法,根据xml的前后缀配置处理生成结果的前后缀,以applyPrefix()为例子。
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!prefixApplied) {
prefixApplied = true;
if (prefixesToOverride != null) {
for (String toRemove : prefixesToOverride) {
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
if (prefix != null) {
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
}
逻辑很清晰,动态sql语句的生成基本就如上所示,别的标签实现略有不同,但参照上面的例子都不复杂。