Previously there was a share of Mybatis application 9 dynamic SQL tags. So how does this tag work in practice?
SqlNode
SQL > update,trim,if; SQL > update,trim,if
package org.apache.ibatis.scripting.xmltags;
/** * SQL node information **@author Clinton Begin
*/
public interface SqlNode {
/** * parse the node **@paramContext contains input information for the Mapper call, as well as built-in _parameter and _databaseId parameter information * *@return* /
boolean apply(DynamicContext context);
}
Copy the code
MixedSqlNode
Where SQL (< INSERT >,< DELETE >,< UPDATE >,< SELECT >) tags in the XML are converted to a MixedSqlNode object. MixedSqlNode has a property contents in it. Contains all dynamic tags for SQL tags (< WHERE >,<if>…) Information is then used to parse SQL statements based on dynamic label information. So what he does is he iterates through the contents property to parse the other property tags
/** * Copyright 2009-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *
* http://www.apache.org/licenses/LICENSE-2.0 * < p > * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
package org.apache.ibatis.scripting.xmltags;
import java.util.List;
/** * Describes a group of SqlNode objects * <select ID ="getStudentsByAlias" resultType="studentDto" useCache="false"> * select * id,name.age,hobby * from student * <where> * <if test="1==1"> * and id is not null * </if> * </where> * </select> * This will be resolved to a StaticTextSqlNode and a WhereSqlNode. The SQL representing the WHERE tag includes the SQL information for the nested IF tag *@author Clinton Begin
*/
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// Call all SqlNode apply methods
contents.forEach(node -> node.apply(context));
return true; }}Copy the code
StaticTextSqlNode
Append text content directly
/** * Copyright 2009-2017 the original author or authors. * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * < p > * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.scripting.xmltags; ** @author Clinton Begin */ public class StaticTextSqlNode implements SqlNode {private final String text; public StaticTextSqlNode(String text) { this.text = text; } @override public Boolean apply(DynamicContext context) {appendSql(text); return true; }}Copy the code
TrimSqlNode
TrimSqlNode has two subclasses. SetSqlNode and WhereSqlNode. The trim tag does the same for the set WHERE tag.
package org.apache.ibatis.scripting.xmltags;
import org.apache.ibatis.session.Configuration;
import java.util.*;
/** * parse the <trim> tag **@author Clinton Begin
*/
public class TrimSqlNode implements SqlNode {
private final SqlNode contents;
private final String prefix;
private final String suffix;
private final List<String> prefixesToOverride;
private final List<String> suffixesToOverride;
private final Configuration configuration;
public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
}
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
// Where set trim All three types of labels may contain child labels, so these labels are stored as MixedSqlNode types. I need to do a second analysis of this
boolean result = contents.apply(filteredDynamicContext);
// Prefix and suffix substitutions
filteredDynamicContext.applyAll();
return result;
}
private static List<String> parseOverrides(String overrides) {
if(overrides ! =null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|".false);
final List<String> list = new ArrayList<>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
private class FilteredDynamicContext extends DynamicContext {
private DynamicContext delegate;
private boolean prefixApplied;
private boolean suffixApplied;
private StringBuilder sqlBuffer;
public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
this.delegate = delegate;
this.prefixApplied = false;
this.suffixApplied = false;
this.sqlBuffer = new StringBuilder();
}
public void applyAll(a) {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
if (trimmedUppercaseSql.length() > 0) {
// Prefix substitution
applyPrefix(sqlBuffer, trimmedUppercaseSql);
// suffix substitution
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
delegate.appendSql(sqlBuffer.toString());
}
@Override
public Map<String, Object> getBindings(a) {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public int getUniqueNumber(a) {
return delegate.getUniqueNumber();
}
@Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
@Override
public String getSql(a) {
return delegate.getSql();
}
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); }}}private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if(! suffixApplied) { suffixApplied =true;
if(suffixesToOverride ! =null) {
for (String toRemove : suffixesToOverride) {
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
break; }}}if(suffix ! =null) {
sql.append("");
sql.append(suffix);
}
}
}
}
}
Copy the code
ForeachSqlNode
Parse the <foreach> tag
package org.apache.ibatis.scripting.xmltags;
import org.apache.ibatis.parsing.GenericTokenParser;
import org.apache.ibatis.session.Configuration;
import java.util.Map;
/** * parse the <foreach> tag **@author Clinton Begin
*/
public class ForEachSqlNode implements SqlNode {
public static final String ITEM_PREFIX = "__frch_";
private final ExpressionEvaluator evaluator;
private final String collectionExpression;
private final SqlNode contents;
private final String open;
private final String close;
private final String separator;
private final String item;
private final String index;
private final Configuration configuration;
public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
this.evaluator = new ExpressionEvaluator();
this.collectionExpression = collectionExpression;
this.contents = contents;
this.open = open;
this.close = close;
this.separator = separator;
this.index = index;
this.item = item;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
Map<String, Object> bindings = context.getBindings();
// Get an iterator to configure the collection parameter collection
finalIterable<? > iterable = evaluator.evaluateIterable(collectionExpression, bindings);if(! iterable.iterator().hasNext()) {return true;
}
boolean first = true;
// Add the open attribute
applyOpen(context);
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
// The first one does not add separator
if (first || separator == null) {
context = new PrefixedContext(context, "");
} else {
context = new PrefixedContext(context, separator);
}
int uniqueNumber = context.getUniqueNumber();
// Issue #709
// Store the iterated values in the map
if (o instanceof Map.Entry) {
@SuppressWarnings("unchecked")
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
applyIndex(context, mapEntry.getKey(), uniqueNumber);
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
applyIndex(context, i, uniqueNumber);
applyItem(context, o, uniqueNumber);
}
// there are other dynamic tags in
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if(first) { first = ! ((PrefixedContext) context).isPrefixApplied(); } context = oldContext; i++; }// Add the close attribute
applyClose(context);
// Clear the collection properties of the binding
context.getBindings().remove(item);
context.getBindings().remove(index);
return true;
}
private void applyIndex(DynamicContext context, Object o, int i) {
if(index ! =null) { context.bind(index, o); context.bind(itemizeItem(index, i), o); }}private void applyItem(DynamicContext context, Object o, int i) {
if(item ! =null) { context.bind(item, o); context.bind(itemizeItem(item, i), o); }}private void applyOpen(DynamicContext context) {
if(open ! =null) { context.appendSql(open); }}private void applyClose(DynamicContext context) {
if(close ! =null) { context.appendSql(close); }}private static String itemizeItem(String item, int i) {
return ITEM_PREFIX + item + "_" + i;
}
private static class FilteredDynamicContext extends DynamicContext {
private final DynamicContext delegate;
private final int index;
private final String itemIndex;
private final String item;
public FilteredDynamicContext(Configuration configuration, DynamicContext delegate, String itemIndex, String item, int i) {
super(configuration, null);
this.delegate = delegate;
this.index = i;
this.itemIndex = itemIndex;
this.item = item;
}
@Override
public Map<String, Object> getBindings(a) {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public String getSql(a) {
return delegate.getSql();
}
@Override
public void appendSql(String sql) {
GenericTokenParser parser = new GenericTokenParser("# {"."}", content -> {
String newContent = content.replaceFirst("^\s*" + item + "(? ! [^.,:\s])", itemizeItem(item, index));
if(itemIndex ! =null && newContent.equals(content)) {
newContent = content.replaceFirst("^\s*" + itemIndex + "(? ! [^.,:\s])", itemizeItem(itemIndex, index));
}
return "# {" + newContent + "}";
});
delegate.appendSql(parser.parse(sql));
}
@Override
public int getUniqueNumber(a) {
returndelegate.getUniqueNumber(); }}private class PrefixedContext extends DynamicContext {
private final DynamicContext delegate;
private final String prefix;
private boolean prefixApplied;
public PrefixedContext(DynamicContext delegate, String prefix) {
super(configuration, null);
this.delegate = delegate;
this.prefix = prefix;
this.prefixApplied = false;
}
public boolean isPrefixApplied(a) {
return prefixApplied;
}
@Override
public Map<String, Object> getBindings(a) {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public void appendSql(String sql) {
if(! prefixApplied && sql ! =null && sql.trim().length() > 0) {
delegate.appendSql(prefix);
prefixApplied = true;
}
delegate.appendSql(sql);
}
@Override
public String getSql(a) {
return delegate.getSql();
}
@Override
public int getUniqueNumber(a) {
returndelegate.getUniqueNumber(); }}}Copy the code
VarDeclSqlNode
Parsing of the <bind> tag
package org.apache.ibatis.scripting.xmltags; ** @author Frank D. Martinez [mnesarco] */ public class VarDeclSqlNode implements SqlNode {private final String name; private final String expression; public VarDeclSqlNode(String var, String exp) { name = var; expression = exp; } @override public Boolean apply(DynamicContext context) {final Object value = OgnlCache.getValue(expression, context.getBindings()); #{likeName} context.bind(name, value); return true; }}Copy the code
ChooseSqlNode
Parse the Choose When Otherwise tag
package org.apache.ibatis.scripting.xmltags;
import java.util.List;
/** * parse the <choose> tag **@author Clinton Begin
*/
public class ChooseSqlNode implements SqlNode {
/**
Tag content */
private final SqlNode defaultSqlNode;
/**
Tag collection */
private final List<SqlNode> ifSqlNodes;
public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
@Override
public boolean apply(DynamicContext context) {
// The condition under the
tag has been converted to IfSqlNode and will be returned if the condition is met
for (SqlNode sqlNode : ifSqlNodes) {
if (sqlNode.apply(context)) {
return true; }}// If the condition is not met, the last
is selected to concatenate the SQL
if(defaultSqlNode ! =null) {
defaultSqlNode.apply(context);
return true;
}
return false; }}Copy the code
IfSqlNode
Parse SQL tags. The value under the if tag is parsed to determine if the condition is met
package org.apache.ibatis.scripting.xmltags;
/** * parse the <if> tag **@author Clinton Begin
*/
public class IfSqlNode implements SqlNode {
/** * is used to parse the OGNL expression */
private final ExpressionEvaluator evaluator;
/** * Save the test attribute */ in the
tag
private final String test;
/** * save the SQL information in
tag */
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 the OGNL expression resolves to true, the APPLY method of the SqlNode corresponding to the
tag content is called
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false; }}Copy the code