最近在項目使用mybatis中碰到個問題
- <if test="type=='y'">
- and status = 0
- </if>
當傳入的type的值為y的時候,if判斷內的sql也不會執行,抱着這個疑問就去看了mybatis是怎么解析sql的。下面我們一起來看一下mybatis 的執行過程。
DefaultSqlSession.class 121行
- 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是一個接口。
- /**
- *
- * This bean represets the content of a mapped statement read from an XML file
- * or an annotation. It creates the SQL that will be passed to the database out
- * of the input parameter received from the user.
- *
- */
- public interface SqlSource {
- BoundSql getBoundSql(Object parameterObject);
- }
類中getBoundSql是一個核心方法,mybatis 也是通過這個方法來為我們構建sql。BoundSql 對象其中保存了經過參數解析,以及判斷解析完成sql語句。比如<if> <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是一個接口
- public interface SqlNode {
- boolean apply(DynamicContext context);
- }
可以看到類中 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"解析出來是一個字符串,兩者相等!