遇到个奇怪的问题。mapper.xml中是这么写的
AND STATUS IN
#{status ,jdbcType=TINYINT}
AND STATUS = #{status,jdbcType=TINYINT}
当入参输入statusList([1,2,3])时,sql如下:
AND STATUS IN (1,2,3)AND STATUS = 3
很奇怪为什么status无值,反而在if判断时被判定为有值,而且为3,结合上面的条件,发现foreach的item中出现了同名的status,怀疑是否mybatis底层在做判断解析时,将item放入全局的参数中,导致出现参数的覆盖。于是开始源码分析。
对于mybatis的执行原理详解,请阅读我的另一篇博文《通俗易懂的Mybatis工作原理》
按照mybatis的原理可知,最终是由sqlSession来执行sql,那么我们想要验证猜想,需要找到mybatis获取完整sql的地方,进行调试分析。以查询为例。
// DefaultSqlSession
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// mapper.xml和mapper.java整合后的实体
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行器(默认创建的是CachingExecutor)执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// CachingExecutor.query
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据参数获取sql,这是我们重点分析的地方
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// MappedStatement.getBoundSql
public BoundSql getBoundSql(Object parameterObject) {
// 根据参数获取sql,这是我们重点分析的地方
// sqlSource类型为DynamicSqlSource
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
public class DynamicSqlSource implements SqlSource {
private Configuration configuration;
// 此方法对应命名空间的sql标签
private SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
// 配置和参数的上下文
DynamicContext context = new DynamicContext(configuration, parameterObject);
// 将各种条件标签解析,如:if/foreach等
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
// ForEachSqlNode
// 解析
@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;
// 拼接foreach中定义的open内容
applyOpen(context);
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
if (first) {
context = new PrefixedContext(context, "");
} else if (separator != null) {
context = new PrefixedContext(context, separator);
} else {
context = new PrefixedContext(context, "");
}
int uniqueNumber = context.getUniqueNumber();
// Issue #709
if (o instanceof Map.Entry) {
@SuppressWarnings("unchecked")
Map.Entry
也就是说context的bindings是一个hashMap,用于存放参数,当执行foreach的解析时,会把foreach的item放入这个hashMap中,然后不停覆盖,直至保留最后一个值;如果参数中一开始定义了一个和item同名的参数,则会被覆盖。这也是为什么我的status没有赋值却依然显示有值的原因
欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!
公众号:帝都的雁