Mybatis SqlNode源码解析
1.ForEachSqlNode
mybatis的foreach标签可以将列表、数组中的元素拼接起来,中间可以指定分隔符separator
上面这段select sql代码使用了foreach标签,传入了一个userIdList的列表,首先会转化为一个ForeachSqlNode对象,经过处理后foreach标签里面的代码会解析成 (假设userIdList=[101,102,103])
(#{__frch_userId_0}, #{__frch_userId_1},#{__frch_userId_2}) , 后续预处理值替换后就会变成 (101,102,103)
下面是具体的ForEachSqlNode的解析过程源码:
public class ForEachSqlNode implements SqlNode {
public static final String ITEM_PREFIX = "__frch_";
// 表达式值获取器
private final ExpressionEvaluator evaluator;
// collection userIdList
private final String collectionExpression;
private final SqlNode contents;
// open值 (
private final String open;
// close值 )
private final String close;
// separator分隔符值 ,
private final String separator;
// item值 userId
private final String item;
// index值 null
private final String index;
// mybatis配置信息
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 bindings = context.getBindings();
// 迭代器
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
// 添加open值
applyOpen(context);
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
// 第一次循环first为true,使用new PrefixedContext(context, "")构建context,因为第一个元素之前不用添加分隔符
// 第一次循环完毕后first为false,使用new PrefixedContext(context, separator)构建,之后先添加分隔符再添加sql值
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) {
// map的index为key,item为value
@SuppressWarnings("unchecked")
Map.Entry
2.TrimSqlNode
mybatis的trim标签可以添加/删除指定的前缀、后缀值
这段代码使用了trim标签,会先匹配去除and | or开头的
where user_id = #{userId}
下面是具体的ForEachSqlNode的解析过程源码:
public class TrimSqlNode implements SqlNode {
private final SqlNode contents;
private final String prefix;
private final String suffix;
private final List prefixesToOverride;
private final List 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 prefixesToOverride, String suffix, List suffixesToOverride) {
// 待处理的sql节点
this.contents = contents;
// 添加前缀
this.prefix = prefix;
// 要去除的前缀
this.prefixesToOverride = prefixesToOverride;
// 添加后缀
this.suffix = suffix;
// 要去除的后缀
this.suffixesToOverride = suffixesToOverride;
// mybatis配置
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
// 处理节点 文本添加到sqlBuffer中
boolean result = contents.apply(filteredDynamicContext);
filteredDynamicContext.applyAll();
return result;
}
// 解析 prefixOverrides和suffixOverrides多个可用|分割
private static List parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List 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);
// 委托原始的context
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);
}
// 想上下文追加sql内容
delegate.appendSql(sqlBuffer.toString());
}
@Override
public Map 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)) {
// 从头开始删除匹配的字符toRemove长度
sql.delete(0, toRemove.trim().length());
break;
}
}
}
if (prefix != null) {
// sql.insert(0, prefix + " ");
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);
}
}
}
}
}
另外其实
去除首尾的逗号, 添加前缀SET
public class SetSqlNode extends TrimSqlNode {
private static final List COMMA = Collections.singletonList(",");
public SetSqlNode(Configuration configuration,SqlNode contents) {
super(configuration, contents, "SET", COMMA, null, COMMA);
}
}
去除开头的AND/OR值,添加前缀WHERE
public class WhereSqlNode extends TrimSqlNode {
private static List 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);
}
}