mybatis专栏
https://blog.csdn.net/worn_xiao/category_6530299.html?spm=1001.2014.3001.5482
一 mybatis动态SQL
1.1 什么是动态SQL
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL 的表达式来消除其他元素。
1.2 设计模式
1.2.1组合模式
组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。我们通过下面的实例来演示组合模式的用法。实例演示了一个组织中员工的层次结构。
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
何时使用:
- 您想表示对象的部分-整体层次结构(树形结构)。
2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。
关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
应用实例:
mybatis动态sql利用了组合模式,其中 SqlNode是组件,MixedSqlNode是树干,其他的IfSqlNode之类特定的SqlNode就是具体的树叶节点.
缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理。
注意事项:定义时为具体类。
1.2.2装饰者模式
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
关键代码:
1、Component 类充当抽象角色,不应该具体实现。
2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
应用实例:
mybatis动态sql上下文类DynamicContext这个也可以应用了装饰者模式,利用了不同的方法对sql进行解析处理
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
1.2.3组合类图
如上图所示就是组合模式的sqlnode类图了详细的代码解释我们放到工作原理里面去.
1.2.4动态sql上下文类图
二 工作原理与实践
2.1动态工作原理
2.1.1 流程图
2.1.2 源码解析
Test 类
public void shouldTrimWHEREInsteadOfORForSecondCondition() throws Exception {
final String expected = "SELECT * FROM BLOG WHERE NAME = ?";
DynamicSqlSource source = createDynamicSqlSource(
new TextSqlNode("SELECT * FROM BLOG"),
new WhereSqlNode(new Configuration(),mixedContents(
new IfSqlNode(mixedContents(new TextSqlNode(" and ID = ? ")), "false"),
new IfSqlNode(mixedContents(new TextSqlNode(" or NAME = ? ")), "true"))));
BoundSql boundSql = source.getBoundSql(null);
assertEquals(expected, boundSql.getSql());
}
如上代码所示是动态sql的一个测试代码,其实这些sql片段最初在mybatis启动的时候都会加载到内存的,执行的方式跟上面大致是一样的了.
private DynamicSqlSource createDynamicSqlSource(SqlNode... contents) throws IOException, SQLException {
createBlogDataSource();
final String resource = "org/apache/ibatis/builder/MapperConfig.xml";
final Reader reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
Configuration configuration = sqlMapper.getConfiguration();
MixedSqlNode sqlNode = mixedContents(contents);
//创建动态数据源
return new DynamicSqlSource(configuration, sqlNode);
}
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public BoundSql getBoundSql(Object parameterObject) {
//创建动态sql上下文
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context); //执行动态sql
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//获取动态sql解析后的sql语句,对sql语句中的${name}这样的占位符号做特殊处理
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
public class SqlSourceBuilder extends BaseBuilder {
private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<>();
private Class<?> parameterType;
private MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public String handleToken(String content) {
${name} 替换占位符参数,替换成?号 并构建参数映射集合
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + parameterProperties);
}
}
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
}
}
}
}
如上所示源代码就是通过把动态sql解析成字符串的StringBuilder再转化为字符串,然后做一个特殊字符的一个替换.给到预编译去处理的整个过程.那么理解了动态sql的工作原理以后.我们来看看动态sql的整个解析过程.如上所示一个是GenericTokenParser他会解析指定开闭字符里面的占位符号并通过TokenHandler的handleToken(String content)方法来对实际的占位符做替换.那么我们mybatis里面具体都是替换来什么呢? TokenHandler有打个实现类ParameterMappingTokenHandler.其中可以看到handleToken(String content) 把${name} 这样的占位符号都替换成了预编译所需要的?字符串了.这个过程顺便构造了ParameterMapping这个参数映射类.
2.2 SQLNode节点
2.2.1 DynamicContext
public class DynamicContext {
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
static {
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}
private final ContextMap bindings;
private final StringBuilder sqlBuilder = new StringBuilder();
private int uniqueNumber = 0;
public DynamicContext(Configuration configuration, Object parameterObject) {
if (parameterObject != null && !(parameterObject instanceof Map)) {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
public Map<String, Object> getBindings() {
return bindings;
}
public void bind(String name, Object value) {
bindings.put(name, value);
}
public void appendSql(String sql) {
sqlBuilder.append(sql);
sqlBuilder.append(" ");
}
public String getSql() {
return sqlBuilder.toString().trim();
}
public int getUniqueNumber() {
return uniqueNumber++;
}
static class ContextMap extends HashMap<String, Object> {
private static final long serialVersionUID = 2977601501966151582L;
private MetaObject parameterMetaObject;
public ContextMap(MetaObject parameterMetaObject) {
this.parameterMetaObject = parameterMetaObject;
}
@Override
public Object get(Object key) {
String strKey = (String) key;
if (super.containsKey(strKey)) {
return super.get(strKey);
}
if (parameterMetaObject != null) {
return parameterMetaObject.getValue(strKey);
}
return null;
}
}
如上代码所示就是动态sql上下文的核心代码了.如上所示我们比较关注的是两个东西.第一是sql解析以后放到哪里存储,第二便是参数是怎么放进去,到时候又是怎么把sql中 #{username}这样的占位符号,替换成预编译的问号,然后传递参数进行sql的执行的.可以看到代码里面sqlBuilder里面就是用来存储sql字符串的.ContextMap bindings可以看到如上这个核心代码,它继承了hashmap是用来存放全局参数的.这里还要讲解一个工具类.
2.2.2 SqlNode
public interface SqlNode {
boolean apply(DynamicContext context);
}
2.2.3 动态SQL:if 原理
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
select * from user where
<if test="username != null">username=#{username}</if>
<if test="username != null">and sex=#{sex} </if>
</select>
public class IfSqlNode implements 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();
}
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
如上代码所示,test就是对应的ognl表达示,通过binding获取表达式对应的参数.解析得到ognl表达式的结果如下所示就可以得到一个ognl的结果值.如果为真的话又继续进行sqlnode解析,实际上就是一个teXtSqlNode.
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;
}
2.2.4动态SQL:trim 原理
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
select * from user
<trim prefix="where" prefixOverrides="and | or">
<if test="username != null"> and username=#{username}</if>
<if test="sex != null"> and sex=#{sex}</if>
</trim>
</select>
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;
}
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);
if (trimmedUppercaseSql.length() > 0) {
applyPrefix(sqlBuffer, trimmedUppercaseSql);
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
delegate.appendSql(sqlBuffer.toString());
}
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
public void bind(String name, Object value) {
delegate.bind(name, value);
}
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
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);
}}}
}
}
applyPrefix,applySuffix 核心代码可以看出来首先是从sql语句中去除前或者后缀集合的关键词,然后再在准备语句的前面或者后面补上前缀关键词或者后缀关键词.因为where,set,foreach语句都有可能会由于前缀或者后缀的问题需要在前后去除关键词,所以我们后面的这三个语句是需要继承自trim的SqlNode的.也是动态sql编写的核心点.
2.2.5动态SQL:if+where 原理
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
select * from user
<where>
<if test="username != null"> username=#{username} </if>
<if test="username != null"> and sex=#{sex} </if>
</where>
</select>
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, null, null);
}
}
如上就是where语句继承trim这样它会在sql的where 后面去除prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", 里面的关键词.
2.2.6动态SQL:if+set 原理
<update id="updateUserById" parameterType="com.ys.po.User">
update user u
<set>
<if test="username != null and username != ''"> u.username = #{username},</if>
<if test="sex != null and sex != ''">u.sex = #{sex} </if>
</set>
where id=#{id}
</update>
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);
}
}
如上也是一样的它会在set的最后去掉,如果存在的话
2.2.7动态SQL:choose(when,otherwise) 原理
<select id="selectUserByChoose" resultType="com.ys.po.User" parameterType="com.ys.po.User">
select * from user
<where>
<choose>
<when test="id !='' and id != null"> id=#{id} </when>
<when test="username !='' and username != null"> and username=#{username} </when>
<otherwise> and sex=#{sex} </otherwise>
</choose>
</where>
</select>
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;
}
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;
}
}
2.2.8动态SQL:foreach 原理
< select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.ys.po.User">
select * from user
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or"> id=#{id} </foreach>
</where>
</select>
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;
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();
if (o instanceof Map.Entry) {
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++;
}
applyClose(context);
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() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public String getSql() {
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() {
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;
}
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
public void bind(String name, Object value) {
delegate.bind(name, value);
}
public void appendSql(String sql) {
if (!prefixApplied && sql != null && sql.trim().length() > 0) {
delegate.appendSql(prefix);
prefixApplied = true;
}
delegate.appendSql(sql);
}
public String getSql() {
return delegate.getSql();
}
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
}
foreach语句就不用说了,其实源代码就是把传入的参数集合转化为一个迭代集合进行迭代填入信息.可以通过如下代码所示看到
public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value == null) {
throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
}
if (value instanceof Iterable) {
return (Iterable<?>) value;
}
if (value.getClass().isArray()) {
int size = Array.getLength(value);
List<Object> answer = new ArrayList<>();
for (int i = 0; i < size; i++) {
Object o = Array.get(value, i);
answer.add(o);
}
return answer;
}
if (value instanceof Map) {
return ((Map) value).entrySet();
}
throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable.");
}
通过返回一个迭代器.再把值和索引填充到代码中.