1. 程式人生 > >Mybatis 判斷的坑

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"解析出來是一個字串,兩者相等!