【mybatis】IF判断的坑 (实现

  • Post author:
  • Post category:其他




转载


2016年11月29日 18:09:22



  • 5559

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


最近在项目使用mybatis中碰到个问题


Xml代码

收藏代码



  1. <


    if


    test

    =

    “type==’y'”


    >


  2. and

    status

    =

    0



  3. </


    if


    >






当传入的type的值为y的时候,if判断内的sql也不会执行,抱着这个疑问就去看了mybatis是怎么解析sql的。下面我们一起来看一下mybatis 的执行过程。






DefaultSqlSession.class  121行


Java代码

收藏代码



  1. public


    void

    select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {


  2. try

    {

  3. MappedStatement ms = configuration.getMappedStatement(statement);

  4. executor.query(ms, wrapCollection(parameter), rowBounds, handler);

  5. }

    catch

    (Exception e) {


  6. throw

    ExceptionFactory.wrapException(

    “Error querying database.  Cause: ”

    + e, e);

  7. }

    finally

    {

  8. ErrorContext.instance().reset();

  9. }

  10. }










在 executor.query(ms, wrapCollection(parameter), rowBounds, handler);




执行到BaseExecutor.class执行器中的query方法


Java代码

收藏代码



  1. public

    <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)

    throws

    SQLException {

  2. BoundSql boundSql = ms.getBoundSql(parameter);

  3. CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);


  4. return

    query(ms, parameter, rowBounds, resultHandler, key, boundSql);

  5. }




在query的方法中看到boundSql,是通过 ms.getBoundSql(parameter);获取的。






再点进去可以看到MappedStatement.class类中的getBoundSql方法


Java代码

收藏代码



  1. public

    BoundSql getBoundSql(Object parameterObject) {

  2. BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

  3. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();


  4. if

    (parameterMappings ==

    null

    || parameterMappings.size() <=

    0

    ) {

  5. boundSql =

    new

    BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);

  6. }



  7. // check for nested result maps in parameter mappings (issue #30)



  8. for

    (ParameterMapping pm : boundSql.getParameterMappings()) {

  9. String rmId = pm.getResultMapId();


  10. if

    (rmId !=

    null

    ) {

  11. ResultMap rm = configuration.getResultMap(rmId);


  12. if

    (rm !=

    null

    ) {

  13. hasNestedResultMaps |= rm.hasNestedResultMaps();

  14. }

  15. }

  16. }



  17. return

    boundSql;

  18. }






看到其中有sqlSource.getBoundSql(parameterObject); sqlsource是一个接口。


Java代码

收藏代码



  1. /**



  2. *



  3. * This bean represets the content of a mapped statement read from an XML file



  4. * or an annotation. It creates the SQL that will be passed to the database out



  5. * of the input parameter received from the user.



  6. *



  7. */



  8. public


    interface

    SqlSource {


  9. BoundSql getBoundSql(Object parameterObject);


  10. }




类中getBoundSql是一个核心方法,mybatis 也是通过这个方法来为我们构建sql。BoundSql 对象其中保存了经过参数解析,以及判断解析完成sql语句。比如<if> <choose> <when> 都回在这一层完成,具体的完成方法往下看,那最常用sqlSource的实现类是DynamicSqlSource.class






Java代码

收藏代码



  1. public


    class

    DynamicSqlSource

    implements

    SqlSource {



  2. private

    Configuration configuration;


  3. private

    SqlNode rootSqlNode;



  4. public

    DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {


  5. this

    .configuration = configuration;


  6. this

    .rootSqlNode = rootSqlNode;

  7. }



  8. public

    BoundSql getBoundSql(Object parameterObject) {

  9. DynamicContext context =

    new

    DynamicContext(configuration, parameterObject);

  10. rootSqlNode.apply(context);

  11. SqlSourceBuilder sqlSourceParser =

    new

    SqlSourceBuilder(configuration);

  12. Class<?> parameterType = parameterObject ==

    null

    ? Object.

    class

    : parameterObject.getClass();

  13. SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());

  14. BoundSql boundSql = sqlSource.getBoundSql(parameterObject);


  15. for

    (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {

  16. boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());

  17. }


  18. return

    boundSql;

  19. }


  20. }




核心方法是调用了rootSqlNode.apply(context); rootSqlNode是一个接口


Java代码

收藏代码



  1. public


    interface

    SqlNode {


  2. boolean

    apply(DynamicContext context);

  3. }




可以看到类中 rootSqlNode.apply(context); 的方法执行就是一个递归的调用,通过不同的




实现类执行不同的标签,每一次appll是完成了我们<></>一次标签中的sql创建,计算出标签中的那一段sql,mybatis通过不停的递归调用,来为我们完成了整个sql的拼接。那我们主要来看IF的实现类IfSqlNode.class


Java代码

收藏代码



  1. public


    class

    IfSqlNode

    implements

    SqlNode {


  2. private

    ExpressionEvaluator evaluator;


  3. private

    String test;


  4. private

    SqlNode contents;



  5. public

    IfSqlNode(SqlNode contents, String test) {


  6. this

    .test = test;


  7. this

    .contents = contents;


  8. this

    .evaluator =

    new

    ExpressionEvaluator();

  9. }



  10. public


    boolean

    apply(DynamicContext context) {


  11. if

    (evaluator.evaluateBoolean(test, context.getBindings())) {

  12. contents.apply(context);


  13. return


    true

    ;

  14. }


  15. return


    false

    ;

  16. }


  17. }






可以看到IF的实现中,执行了 if (evaluator.evaluateBoolean(test, context.getBindings()))  如果返回是false的话直接返回,否则继续递归解析IF标签以下的标签,并且返回true。那继续来看 evaluator.evaluateBoolean 的方法




Java代码

收藏代码



  1. public


    class

    ExpressionEvaluator {


  2. public


    boolean

    evaluateBoolean(String expression, Object parameterObject) {

  3. Object value = OgnlCache.getValue(expression, parameterObject);


  4. if

    (value

    instanceof

    Boolean)

    return

    (Boolean) value;


  5. if

    (value

    instanceof

    Number)

    return

    !

    new

    BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);


  6. return

    value !=

    null

    ;

  7. }






关键点就在于这里,在OgnlCache.getValue中调用了Ognl.getValue,看到这里恍然大悟,mybatis是使用的OGNL表达式来进行解析的,在OGNL的表达式中,’y’会被解析成字符,因为java是强类型的,char 和 一个string 会导致不等。所以if标签中的sql不会被解析。具体的请参照 OGNL 表达式的语法。到这里,上面的问题终于解决了,只需要把代码修改成:


Xml代码

收藏代码



  1. <


    if


    test

    =

    ‘type==”y”‘


    >


  2. and

    status

    =

    0



  3. </


    if


    >




就可以执行了,这样”y”解析出来是一个字符串,两者相等!