【mybatis】mybatis的if test 字符串的坑

轉自:http://blog.csdn.net/z69183787/article/details/51589171

<if test="type=='y'">  
    and status = 1   
</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 = 1   
</if>  

也可以把代碼修改 'y'.toString()

<if test="type == 'y'.toString()">  
    and status = 1   
</if>  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章