StatementHandler在执行数据库语句时,SQL语句保存在BoundSql中,而BoundSql通过SqlSource得到 SqlSource会通过XMLScriptBuilder来创建,在XMLScriptBuilder创建SqlSource之前会对动态SQL标签进行处理,处理方法是parseDynamicTags 在parseDynamicTags方法中,获取Statement节点的子节点 如果节点是ELEMENT_NODE类型,通过节点名称获取NodeHandler并处理节点,否则设置为TextSqlNode Node类型定义如下 nodeHandlerMap中保存了节点名和对应的NodeHandler的关系,在构造方法中调用initNodeHandlerMap进行初始化 各个节点解析后都会生成SqlNode节点对象,SqlNode会保存到List<SqlNode>中 ,这个集合在所有的节点解析完后会生成MixedSqlNode对象去生成DynamicSqlSource对象 SqlNode是一个接口,只定义了一个apply方法 DynamicSqlSource生成BoundSql的时候,就会调用MixedSqlNode的apply方法,传入的参数context中会附带parameterObject对象,这个是执行数据库操作的参数 而在MixedSqlNode的apply方法中,会遍历所有的SqlNode,回调apply方法,这些SqlNode就是之前NodeHandler解析的节点集合 对于trim节点,对调用TrimHandler的handleNode方法,方法中首先会解析prefix、prefixOverrdes、suffix、suffixOverridex属性,然后创建一个TrimSqlNode对象 TrimSqlNode构造方法会对属性进行赋值,然后在回调的apply方法中调用FilteredDynamicContext的applyAll方法处理 FilteredDynamicContext的applyAll方法中,调用applyPrefix和applySuffix方法将sql解析后放入到DynamicContext的sql属性中 Trim标签的作用在包含的内容上添加前缀和后缀,也可以用来移除动态sql中最前面和最后面多余的字符 对于where节点,会调用WhereHandler的handleNode方法,方法中创建一个WhereSqlNode对象 WhereSqlNode实际上是继承TrimSqlNode,其中prefix固定为WHERE,prefixesToOverride为AND和OR,根据TrimSqlNode的解析,可以知道where节点就是在包含的内容前面加上WHERE,并删除最前面的AND或者OR字符 对于set节点,会调用SetHandler的handleNode方法,方法中创建一个SetSqlNode对象 SetSqlNode也是继承TrimSqlNode,其中prefix固定为SET,prefixesToOverride和suffixesToOverride为“,”,根据TrimSqlNode的解析,可以知道set节点就是在包含的内容前面加上SET,并删除最前和最后面的“,”字符 对于foreach节点,会调用ForEachHandler的handleNode方法,方法中首先会解析collection、item、index、open、close、separator属性,然后创建一个forEachSqlNode对象 ForEachSqlNode的apply方法 对于if节点,会调用IfHandler的handleNode方法,方法中首先会解析test属性,然后创建一个IfSqlNode对象 IfSqlNode的apply方法中,如果test中的条件成立,则将节点内的sql进行拼接 对于choose节点,会调用ChooseHandler的handleNode方法,方法中会对when节点和otherwise节点分别进行解析,对应使用IfHandler和OtherwiseHandler来处理 对于otherwise节点,会调用OtherwiseHandler的handleNode方法,方法中直接将sql添加到targetContents中 对于bind节点,会调用BindHandler的handleNode方法,方法中首先会解析name、value属性,然后创建一个VarDeclSqlNode对象 VarDeclSqlNode的apply方法中,会将bind属性的name和value进行绑定,在参数传递时使用,bind标签可以用于参数的和字符串的拼接形成新的参数 其他的非标签的sql,会创建一个TextSqlNode,首先会通过isDynamic检查如果返回true,则通过TextSqlNode进行处理 否则创建StaticTextSqlNode对象,StaticTextSqlNode中不会对sql进行处理,这里详细看TextSqlNode的处理 isDynamic方法中如果字符串中包含了${},会返回true,则会使用TextSqlNode的apply方法进行处理 apply方法中创建BindingTokenParser和GenericTokenParser对象,然后通过GenericTokenParser的parse方法处理字符串,处理后拼接到最终的sql上 GenericTokenParser中会将openToken和closeToken之间的字符去掉,替代为handler的handleToken方法返回的值 这里handler是BindingTokenParser的实例,handleToken方法中会获取${}中字符串对应的参数的值,然后直接拼接到sql中 现在可以了解到Mybatis中$和#参数的区别,$是直接进行拼接,会有sql注入的风险,#是替换为?,然后通过PrepareStatement预编译传递参数1.XMLScriptBuilder处理动态SQL
public class XMLScriptBuilder extends BaseBuilder {
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));
String nodeName;
//不是CDATA_SECTION_NODE和TEXT_NODE类型
if (child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {
//ELEMENT_NODE节点类型处理
if (child.getNode().getNodeType() == 1) {
nodeName = child.getNode().getNodeName();
//通过Node名称获取NodeHandler
XMLScriptBuilder.NodeHandler handler = (XMLScriptBuilder.NodeHandler)this.nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//使用NodeHander处理Node
handler.handleNode(child, contents);
this.isDynamic = true;
}
} else {
//TextSqlNode处理
nodeName = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(nodeName);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
this.isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(nodeName));
}
}
}
return new MixedSqlNode(contents);
}
...
}
public class XMLScriptBuilder extends BaseBuilder {
private final Map<String, XMLScriptBuilder.NodeHandler> nodeHandlerMap;
private void initNodeHandlerMap() {
this.nodeHandlerMap.put("trim", new XMLScriptBuilder.TrimHandler());
this.nodeHandlerMap.put("where", new XMLScriptBuilder.WhereHandler());
this.nodeHandlerMap.put("set", new XMLScriptBuilder.SetHandler());
this.nodeHandlerMap.put("foreach", new XMLScriptBuilder.ForEachHandler());
this.nodeHandlerMap.put("if", new XMLScriptBuilder.IfHandler());
this.nodeHandlerMap.put("choose", new XMLScriptBuilder.ChooseHandler());
this.nodeHandlerMap.put("when", new XMLScriptBuilder.IfHandler());
this.nodeHandlerMap.put("otherwise", new XMLScriptBuilder.OtherwiseHandler());
this.nodeHandlerMap.put("bind", new XMLScriptBuilder.BindHandler());
}
...
}
2.SqlNode处理
3.TrimHandler
public class XMLScriptBuilder extends BaseBuilder {
private class TrimHandler implements XMLScriptBuilder.NodeHandler {
public TrimHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.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(XMLScriptBuilder.this.configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
targetContents.add(trim);
}
}
...
}
public class TrimSqlNode implements SqlNode {
private class FilteredDynamicContext extends DynamicContext {
public void applyAll() {
this.sqlBuffer = new StringBuilder(this.sqlBuffer.toString().trim());
String trimmedUppercaseSql = this.sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
if (trimmedUppercaseSql.length() > 0) {
this.applyPrefix(this.sqlBuffer, trimmedUppercaseSql);
this.applySuffix(this.sqlBuffer, trimmedUppercaseSql);
}
this.delegate.appendSql(this.sqlBuffer.toString());
}
//前缀处理
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!this.prefixApplied) {
this.prefixApplied = true;
//prefixesToOverride处理
if (TrimSqlNode.this.prefixesToOverride != null) {
//遍历prefixesToOverride中的字符,如果sql以这些字符开头,则删除开头的这几个字符
Iterator var3 = TrimSqlNode.this.prefixesToOverride.iterator();
while(var3.hasNext()) {
String toRemove = (String)var3.next();
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
//将prefix添加到sql最前面
if (TrimSqlNode.this.prefix != null) {
sql.insert(0, " ");
sql.insert(0, TrimSqlNode.this.prefix);
}
}
}
//applySuffix后缀处理
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if (!this.suffixApplied) {
this.suffixApplied = true;
//suffixesToOverride 处理
if (TrimSqlNode.this.suffixesToOverride != null) {
//如果sql是以suffixesToOverride中的字符结果,删除该字符
label33: {
Iterator var3 = TrimSqlNode.this.suffixesToOverride.iterator();
String toRemove;
do {
if (!var3.hasNext()) {
break label33;
}
toRemove = (String)var3.next();
} while(!trimmedUppercaseSql.endsWith(toRemove) && !trimmedUppercaseSql.endsWith(toRemove.trim()));
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
}
}
//将suffix的字符添加到sql最后
if (TrimSqlNode.this.suffix != null) {
sql.append(" ");
sql.append(TrimSqlNode.this.suffix);
}
}
}
...
}
}
4.WhereHandler
public class XMLScriptBuilder extends BaseBuilder {
private class WhereHandler implements XMLScriptBuilder.NodeHandler {
public WhereHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode);
targetContents.add(where);
}
}
...
}
public class WhereSqlNode extends TrimSqlNode {
private static List<String> prefixList = Arrays.asList("AND ", "OR ", "AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, (String)null, (List)null);
}
}
5.SetHandler
public class XMLScriptBuilder extends BaseBuilder {
private class SetHandler implements XMLScriptBuilder.NodeHandler {
public SetHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
SetSqlNode set = new SetSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode);
targetContents.add(set);
}
}
...
}
public class SetSqlNode extends TrimSqlNode {
private static final List<String> COMMA = Collections.singletonList(",");
public SetSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "SET", COMMA, (String)null, COMMA);
}
}
6.ForEachHandler
public class XMLScriptBuilder extends BaseBuilder {
private class ForEachHandler implements XMLScriptBuilder.NodeHandler {
public ForEachHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
String collection = nodeToHandle.getStringAttribute("collection");
String item = nodeToHandle.getStringAttribute("item");
String index = nodeToHandle.getStringAttribute("index");
String open = nodeToHandle.getStringAttribute("open");
String close = nodeToHandle.getStringAttribute("close");
String separator = nodeToHandle.getStringAttribute("separator");
ForEachSqlNode forEachSqlNode = new ForEachSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode, collection, index, item, open, close, separator);
targetContents.add(forEachSqlNode);
}
}
...
}
public class ForEachSqlNode implements SqlNode {
public boolean apply(DynamicContext context) {
Map<String, Object> bindings = context.getBindings();
Iterable<?> iterable = this.evaluator.evaluateIterable(this.collectionExpression, bindings);
if (!iterable.iterator().hasNext()) {
return true;
} else {
boolean first = true;
//拼接open字符串
this.applyOpen(context);
int i = 0;
//遍历collection集合
for(Iterator var6 = iterable.iterator(); var6.hasNext(); ++i) {
Object o = var6.next();
ForEachSqlNode.PrefixedContext context;
//根据separator创建PrefixedContext
if (!first && this.separator != null) {
context = new ForEachSqlNode.PrefixedContext(context, this.separator);
} else {
context = new ForEachSqlNode.PrefixedContext(context, "");
}
//重新设置参数
int uniqueNumber = context.getUniqueNumber();
if (o instanceof Entry) {
Entry<Object, Object> mapEntry = (Entry)o;
this.applyIndex(context, mapEntry.getKey(), uniqueNumber);
this.applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
this.applyIndex(context, i, uniqueNumber);
this.applyItem(context, o, uniqueNumber);
}
this.contents.apply(new ForEachSqlNode.FilteredDynamicContext(this.configuration, context, this.index, this.item, uniqueNumber));
if (first) {
first = !((ForEachSqlNode.PrefixedContext)context).isPrefixApplied();
}
context = context;
}
//拼接close字符串
this.applyClose(context);
//移除参数
context.getBindings().remove(this.item);
context.getBindings().remove(this.index);
return true;
}
}
...
}
7.IfHandler
public class XMLScriptBuilder extends BaseBuilder {
private class IfHandler implements XMLScriptBuilder.NodeHandler {
public IfHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
}
...
}
8.ChooseHandler
9.OtherwiseHandler
public class XMLScriptBuilder extends BaseBuilder {
private class OtherwiseHandler implements XMLScriptBuilder.NodeHandler {
public OtherwiseHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
targetContents.add(mixedSqlNode);
}
}
...
}
10.BindHandler
public class XMLScriptBuilder extends BaseBuilder {
private class BindHandler implements XMLScriptBuilder.NodeHandler {
public BindHandler() {
}
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
String name = nodeToHandle.getStringAttribute("name");
String expression = nodeToHandle.getStringAttribute("value");
VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
targetContents.add(node);
}
}
...
}
public class VarDeclSqlNode implements SqlNode {
private final String name;
private final String expression;
public VarDeclSqlNode(String var, String exp) {
this.name = var;
this.expression = exp;
}
public boolean apply(DynamicContext context) {
Object value = OgnlCache.getValue(this.expression, context.getBindings());
context.bind(this.name, value);
return true;
}
}
11.TextSqlNode处理
public class TextSqlNode implements SqlNode {
public boolean apply(DynamicContext context) {
GenericTokenParser parser = this.createParser(new TextSqlNode.BindingTokenParser(context, this.injectionFilter));
context.appendSql(parser.parse(this.text));
return true;
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
...
}
public class TextSqlNode implements SqlNode {
private static class BindingTokenParser implements TokenHandler {
private DynamicContext context;
private Pattern injectionFilter;
public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
}
public String handleToken(String content) {
Object parameter = this.context.getBindings().get("_parameter");
if (parameter == null) {
this.context.getBindings().put("value", (Object)null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
this.context.getBindings().put("value", parameter);
}
Object value = OgnlCache.getValue(content, this.context.getBindings());
String srtValue = value == null ? "" : String.valueOf(value);
this.checkInjection(srtValue);
return srtValue;
}
private void checkInjection(String value) {
if (this.injectionFilter != null && !this.injectionFilter.matcher(value).matches()) {
throw new ScriptingException("Invalid input. Please conform to regex" + this.injectionFilter.pattern());
}
}
}
}