zoukankan      html  css  js  c++  java
  • 【mybatis】IF判断的坑

    http://cheng-xinwei.iteye.com/blog/2008200

    最近在项目使用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"解析出来是一个字符串,两者相等!

  • 相关阅读:
    设计模式
    mysql引擎与物理文件
    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'socialCode' in 'class java.lang.String'
    bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException
    jedis异常Broken pipe (Write failed)
    java第三方工具包
    mysql安装(centos7)
    sftp安装(linux)
    Snmp oid对应信息
    RAID
  • 原文地址:https://www.cnblogs.com/daimzh/p/12854492.html
Copyright © 2011-2022 走看看