Mybatis 判斷的坑
<if test="type=='y'">
and status = 0
</if>
當傳入的type的值為y的時候,if判斷內的sql也不會執行,抱著這個疑問就去看了mybatis是怎麼解析sql的。下面我們一起來看一下mybatis 的執行過程。
DefaultSqlSession.class
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在 executor.query(ms, wrapCollection(parameter), rowBounds, handler);
執行到BaseExecutor.class執行器中的query方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在query
的方法中看到boundSql
,是通過 ms.getBoundSql(parameter);
獲取的。
再點進去可以看到MappedStatement.class
類中的getBoundSql
方法
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.size() <= 0) {
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;
}
看到其中有sqlSource.getBoundSql(parameterObject);
sqlsource
是一個介面。
類中getBoundSql
是一個核心方法,mybatis 也是通過這個方法來為我們構建sql。BoundSql
物件其中儲存了經過引數解析,以及判斷解析完成sql語句。比如 <choose> <when>
都會在這一層完成,具體的完成方法往下看,那最常用sqlSource
的實現類是DynamicSqlSource.class
public class DynamicSqlSource implements SqlSource {
private Configuration configuration;
private SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
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<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
核心方法是呼叫了rootSqlNode.apply(context);
rootSqlNode
是一個介面 .
可以看到類中 rootSqlNode.apply(context);
的方法執行就是一個遞迴的呼叫,通過不同的
實現類執行不同的標籤,每一次appll是完成了我們<></>
一次標籤中的sql建立,計算出標籤中的那一段sql,mybatis通過不停的遞迴呼叫,來為我們完成了整個sql的拼接。那我們主要來看IF的實現類IfSqlNode.class
public class IfSqlNode implements SqlNode {
private ExpressionEvaluator evaluator;
private String test;
private 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;
}
}
可以看到IF的實現中,執行了 if (evaluator.evaluateBoolean(test, context.getBindings()))如果返回是false的話直接返回,否則繼續遞迴解析IF標籤以下的標籤,並且返回
true。那繼續來看
evaluator.evaluateBoolean` 的方法
public class ExpressionEvaluator {
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)).equals(BigDecimal.ZERO);
return value != null;
}
關鍵點就在於這裡,在OgnlCache.getValue
中呼叫了Ognl.getValue
,看到這裡恍然大悟,mybatis是使用的OGNL表示式來進行解析的,在OGNL的表示式中,'y'
會被解析成字元,因為java是強型別的,char
和 一個String
會導致不等。所以if標籤
中的sql不會被解析。具體的請參照 OGNL 表示式的語法。到這裡,上面的問題終於解決了,只需要把程式碼修改成:
<if test='type=="y"'> //注意是雙引號,不是單引號!!!
and status = 0
</if>
就可以執行了,這樣"y"
解析出來是一個字串,兩者相等!