sqlNode 如其名,用于存储 sql 的 Node 节点,Mybatis 中可使用 if、where 等标签,也就是说,拥有对应的 Node 节点进行相应的节点进行处理。
1. SqlNode 接口
- 就一个简单的 apply 抽象函数,具有 八个直接子类实现类
public interface SqlNode {
boolean apply(DynamicContext context);
}
1.1 MixedSqlNode
- 混合的 sqlNode,如代码
List<SqlNode>
所示,存储多个 SqlNode
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
/* 遍历 list,执行每个 sqlNode 的 apply 方法 */
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
1.2 StaticTextSqlNode
- 静态文本 sql ,即非动态 sql,如
select * from user
public class StaticTextSqlNode implements SqlNode {
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
/* 直接添加进 context 即可 */
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
1.3 TextSqlNode
- 文本 sql 节点:用于判断是否为 动态 sql。简单理解,多种 SqlNode 需要有人分辨,该类就是干这份工作
public class TextSqlNode implements SqlNode {
private final String text;
private final Pattern injectionFilter;
public TextSqlNode(String text) {
this(text, null);
}
public TextSqlNode(String text, Pattern injectionFilter) {
this.text = text;
this.injectionFilter = injectionFilter;
}
/* 判断是否为 动态的sql */
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
/* 创建解析器,handler:解析器的处理器 */
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
/* 静态内部类,绑定 Token 的解析器 */
private static class BindingTokenParser implements TokenHandler {
private DynamicContext context;
/* 用于验证 ${id} 返回的值,在 Mybatis 中没有实际应用 */
private Pattern injectionFilter;
public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
}
@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;
}
private void checkInjection(String value) {
if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
}
}
}
/* 动态监测解析器,主要用于设置 sql 的动态性 */
private static class DynamicCheckerTokenParser implements TokenHandler {
private boolean isDynamic;
public DynamicCheckerTokenParser() {
// Prevent Synthetic Access
}
public boolean isDynamic() {
return isDynamic;
}
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
}
}
- 结合 debugger 可看到,TextSqlNode 主要用于判断属于 TEXT_NODE 的 sql 是否属于 动态 sql 即是否含有 ${}
1.4 ForEachSqlNode
- sql 语句有对应的批量操作,该类的作用就是遍历传入的集合并进行相应的处理
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();
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true; // 第一个元素的标志位
/* 集合对象遍历开始前,context 添加 open 标志即 "(" */
applyOpen(context);
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
/* 判断是否为第一个元素 || 没有使用分隔符 */
if (first || separator == null) {
context = new PrefixedContext(context, "");
} else {
context = new PrefixedContext(context, separator);
}
int uniqueNumber = context.getUniqueNumber();
// Issue #709
if (o instanceof Map.Entry) {
/* 若对象o 为 Entry 的实例,获取 key 值作为 index与item 的key值,否则按顺序作为key值(从0开始) */
@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);
}
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if (first) {
first = !((PrefixedContext) context).isPrefixApplied();
}
context = oldContext;
i++;
}
/* 集合对象遍历结束,context 添加 close 标志即 ")" */
applyClose(context);
context.getBindings().remove(item);
context.getBindings().remove(index);
return true;
}
/* 列表元素与 index 进行绑定 */
private void applyIndex(DynamicContext context, Object o, int i) {
if (index != null) {
context.bind(index, o);
context.bind(itemizeItem(index, i), o);
}
}
/* 列表元素与 item 绑定 */
private void applyItem(DynamicContext context, Object o, int i) {
if (item != null) {
context.bind(item, o);
context.bind(itemizeItem(item, i), o);
}
}
/* 添加 open 即 "(" */
private void applyOpen(DynamicContext context) {
if (open != null) {
context.appendSql(open);
}
}
/* 添加 close 即 ")" */
private void applyClose(DynamicContext context) {
if (close != null) {
context.appendSql(close);
}
}
/* ITEM_PREFIX = "__frch_" */
private static String itemizeItem(String item, int i) {
return ITEM_PREFIX + item + "_" + i;
}
/* 该类用于遍历 foreach 中的 sql */
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() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public String getSql() {
return delegate.getSql();
}
/* 添加 foreach 中的 sql */
@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() {
return delegate.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() {
return prefixApplied;
}
@Override
public Map<String, Object> getBindings() {
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() {
return delegate.getSql();
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
}
1.5 IfSqlNode
- if 标签的 sql 节点,用于判断 sql 语句是否需要添加
public class IfSqlNode implements SqlNode {
/* evaluator:test 中的表达式执行器,返回表达式的 boolean 值
* test: 用于存储 if 节点中的 test 判断文本
* contents: 用于存储 if 中的 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();
}
/* 先判断 test 表达式是否为真,若为真,执行 if 节点内的 SqlNod */
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
1.6 VarDeclSqlNode
- 与 bind 节点对应,为上下文变量(单条 sql 语句有效)绑定一个 value
public class VarDeclSqlNode implements SqlNode {
/* name: bind节点中的 name 属性,expression:bind节点中的 value 属性 */
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());
context.bind(name, value);
return true;
}
}
1.7 TrimSqlNode
- 可以为 sql 添加、替换(覆盖) 指定的前缀与后缀
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);
boolean result = contents.apply(filteredDynamicContext);
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() {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
/* 此处的判断就是 where、set 节点内容为空时,不添加前缀的原因 */
if (trimmedUppercaseSql.length() > 0) {
applyPrefix(sqlBuffer, trimmedUppercaseSql);
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
delegate.appendSql(sqlBuffer.toString());
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
@Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
@Override
public String getSql() {
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);
}
}
}
}
}
1.7.1 WhereSqlNode
- where 条件判断,继承于 TrimSqlNode,即与 TrimSqlNode 使用一致, 指定了特定的前缀 WHERE
public class WhereSqlNode extends TrimSqlNode {
/* 此处指定覆盖的前缀,应该是 where 只跟 and or 搭配使用 */
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, null, null);
}
}
1.7.2 SetSqlNode
- set 属性更改,继承于 TrimSqlNode,即与 TrimSqlNode 使用一致, 指定了特定的前缀 SET
public class SetSqlNode extends TrimSqlNode {
/* set 的使用只需要处理 分隔符 "," */
private static final List<String> COMMA = Collections.singletonList(",");
public SetSqlNode(Configuration configuration,SqlNode contents) {
super(configuration, contents, "SET", COMMA, null, COMMA);
}
}
1.8 ChooseSqlNode
- 类似 java 的 switch,when 相当于 case,otherwise 相当于 default
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;
}
}