mybatis在xml中传入整型参数为0时查询条件失效

文章目录

    • Mybatis查询语句sql拼装源码解析
      • 带着问题学习源码(从加载mapper到sql拼装)
        • 问题现象
        • 原因
        • 源码解析(Mybatis-plus)
          • 1、加载SqlSessionFactory
            • xmlConfigBuilder.parse();
            • xmlMapperBuilder.parse();
          • 2、mapper接口生成代理对象
          • 3、调用查询方法
            • DefaultSqlSession代理对象获取sqlSession
            • 装饰者模式创建executor和责任链模式interceptorChain加载插件
          • 继续执行查询
            • 关键查询
            • 解析sql过程
            • sql解析为MixedSqlNode节点
            • rootSqlNode.apply(context)
            • ASTAND节点树进行解析
            • ASTAnd
            • ASTNotEq
            • 4、问题根源
            • ASTChain
            • ASTProperty
            • ASTConst
        • 解决

Mybatis查询语句sql拼装源码解析

带着问题学习源码(从加载mapper到sql拼装)

问题现象

后端用Integer接收0传入param.pushStatus,为什么param.pushStatus !=''判断为false
后端使用Integer接收
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kgpQR0NO-1650516052826)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d896a8c5d9e669c7b1c36a1e4a1610d7)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yhqAzZE8-1650516052827)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/9602944814709df16db8d90d17c9a5d3)]

原因

mapper接口传入的参数类型为Integer值为0时,mybaits 在进行 **param.pushStatus !=‘’**的时候会默认""和0 都转换成double进行比较 都是0.0 ,结果不是重点,重点在于下面过程。

源码解析(Mybatis-plus)

Mybatis-plus很多类重写了Mybatis,此处以Mybatis-plus源码出发

1、加载SqlSessionFactory

项目启动会通过springboot的自动装配原理加载MybatisPlusAutoConfiguration从而加载SqlSessionFactory(加载mapper到MybatisSqlSessionFactoryBean)

public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        ...
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
		//下面的ConfigLocation为:classpath:mybatis/mybatis-config.xml
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));

        }
        ...
		//这里是装载插件(之后会加入责任链中)适用于慢sql查询拦截器等
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }
		...
		//...注入很多属性比如:自定义枚举包、注入主键生成器、注入sql注入器、注入ID生成器等
 		factory.getObject();
		}
		//factory.getObject()--会进行后置属性设置(MybatisSqlSessionFactoryBean)
//MybatisSqlSessionFactoryBean
//这里关注mapper的设置
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        MybatisXMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
            ...
        } else if (this.configLocation != null) {
            // this.configLocation 里面包括mybatis/mybatis-config.xml
			//如果在配置文件有标签解析
            xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            targetConfiguration = xmlConfigBuilder.getConfiguration();
        } else {
            ...
        }
    ......
         if (xmlConfigBuilder != null) {
            try {
                //第一种--解析配置文件mybatis-config.xml信息
                xmlConfigBuilder.parse();
        }
    ......
		//mapperLocations是提前扫描自定义的classpath:mapper/*.xml文件
        //例如:file [E:\code\trade\trade\target\classes\mapper\SellReconciliationMapper.xml]
if (this.mapperLocations != null) {
            if (this.mapperLocations.length == 0) {
               ...
            } else {
			//循环遍历所有的xxxx.xml
                for (Resource mapperLocation : this.mapperLocations) {
                    if (mapperLocation == null) {
                        continue;
                    }
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                        //这里解析mapper
                        xmlMapperBuilder.parse();
                   }
            }
        }
}
xmlConfigBuilder.parse();
public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
    private void parseConfiguration(XNode root) {
        try {
			//将mybatis-config.xml通过XPath解析成XNode再进行解析
            propertiesElement(root.evalNode("properties"));
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            loadCustomVfs(settings);
            loadCustomLogImpl(settings);
            typeAliasesElement(root.evalNode("typeAliases"));
            pluginElement(root.evalNode("plugins"));
            objectFactoryElement(root.evalNode("objectFactory"));
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            settingsElement(settings);
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            typeHandlerElement(root.evalNode("typeHandlers"));
			//这里可以配置mapper
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
xmlMapperBuilder.parse();
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
		//前面已经获取了SellReconciliationMapper.xml,通过命名空间解析mapper
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //再通过命名空间绑定mapper
      bindMapperForNamespace();
    }
	//解析ResultMap等属性
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

private void bindMapperForNamespace() {
    //com.jdh.trade.mapper.SellReconciliationMapper
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
         //获取命名空间获取mapper接口
        boundType = Resources.classForName(namespace);
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        //Spring可能不知道真正的资源名,所以我们设置了一个标志
		//防止从映射器接口再次加载这个资源
        configuration.addLoadedResource("namespace:" + namespace);
         //关键这里将mapper增加到map中
        configuration.addMapper(boundType);
      }
    }
  }
//key mapper接口 value mapper代理工厂
//private final Map, MybatisMapperProxyFactory> knownMappers = new HashMap<>();
@Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            ...
            boolean loadCompleted = false;
            try {
                //!!!!保存到map!!!!
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
                //在运行解析器之前添加类型是很重要的否则,将自动尝试绑定映射器解析器。如果类型已经知道,则不会尝试.
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            }...
    }
2、mapper接口生成代理对象

@service 加载bean之后会通过
doGetObjectFromFactoryBean方法中执行factory.getObject()获取到bean实例MybatisMapperProxy。

//MapperFactoryBean
//对于mybatis相关的mapper
  @Override
  public T getObject() throws Exception {
      //this.mapperInterface 相当于接口com.jdh.trade.mapper.xxxMapper
    return getSqlSession().getMapper(this.mapperInterface);
  }
//SqlSessionTemplate
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
//MybatisMapperRegistry
 @Override
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //knownMappers 这个map是上面保存的
        final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
		...
        try {
            //生成代理对象
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
	
	
//MybatisMapperProxyFactory
//mapperProxyFactory.newInstance(sqlSession);
 protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
     //Proxy 生成动态代理实例
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

3、调用查询方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t4H8ej0Q-1650516052829)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/3f07629179b2a67f00a75a1c969ca3ad)]

执行mapper的查询等语句就会进入代理对象的invoke方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0AArxR6-1650516052829)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/4639d1b1bcbc912c3f7c5a2353ad3da6)]

InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
MybatisMapperProxy
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-43rqFH0W-1650516052830)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/50c83add18d67470cdd256f3a1fe4546)]

从缓存获取MybatisMapperMethod-属性如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YaYw3jeo-1650516052830)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d4fe14619bcf0e7cfef5d3c509f65069)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fDPYeN03-1650516052831)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/3e0d6569d357998f88a74fbe6521245a)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yE1T20pP-1650516052831)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/13a45f906187eca9674e49b990f29602)]

最终执行sql走的是MybatisMapperMethod.execute(sqlSession, args)

public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
		//根据是查询还是更新进入不同分支
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (method.returnsMany()) {
                    //比如 selectList 就会走这里
                    result = executeForMany(sqlSession, args);
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                } else {
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // 分页查询
                    if (IPage.class.isAssignableFrom(method.getReturnType())) {
                        ...
                        //关注这里 执行分页 默认也是执行selectList
                        result = executeForIPage(sqlSession, args);
                        ...
                }
	}
	/**
     * TODO IPage 专用
     */
    private <E> List<E> executeForIPage(SqlSession sqlSession, Object[] args) {
        Object param = method.convertArgsToSqlCommandParam(args);
        //执行SqlSessionTemplate的selectList
        return sqlSession.selectList(command.getName(), param);
    }
	  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
  //DefaultSqlSession
    return this.sqlSessionProxy.selectList(statement, parameter);
  }
DefaultSqlSession代理对象获取sqlSession
	private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //加载sqlSession-getSqlSession-通过sessionFactory.openSession(executorType);
     //从中会获取执行器和加载插件
       SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
		//拿到sqlSession才能去执行查询
        Object result = method.invoke(sqlSession, args);
        ....
    }
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
	...
    //DefaultSqlSessionFactory.openSession
    session = sessionFactory.openSession(executorType);
	...
    return session;
  }
@Override
  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
  }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
	  //新建事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //创建执行器 MybatisConfiguration.newExecutor
		//(默认是simple执行器)
      final Executor executor = configuration.newExecutor(tx, execType);
        //最后返回默认DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
装饰者模式创建executor和责任链模式interceptorChain加载插件
@Override
    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new MybatisBatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new MybatisReuseExecutor(this, transaction);
        } else {
            executor = new MybatisSimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
            //装饰者模式 装饰了简单执行器
            executor = new MybatisCachingExecutor(executor);
        }
        //责任链 进行增加所有执行器 并执行plugin
        //通过判断插件,new Plugin(target, interceptor, signatureMap))生成MybatisCachingExecutor代理对象!!!
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }
继续执行查询

DefaultSqlSession.selectList
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rqw7uuW7-1650516052832)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/7e7eada5ee523b9358e3746bb94237eb)]

//DefaultSqlSession
@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
//rowBounds 是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)
//wrapCollection(parameter)是用来装饰集合或者数组参数 里面有查询条件
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
   	   //statement:
       //名字com.jdh.trade.mapper.SellReconciliationMapper.selectSellReconciliationByPage
       //获取MappedStatement对象,通过配置信息从StrictMap缓存中获取
      MappedStatement ms = configuration.getMappedStatement(statement);
        //执行executor对象里面的query方法
        //这里的executor是在DefaultSqlSessionFactory中,
        //mybatis 通过Configuration对象创建的 对应CachingExecutor  
        //mybatis-plus 通过MybatisConfiguration 创建MybatisCachingExecutor
        //根据不同的配置,会有不同的Executor 无论那个执行器查询最终都会到下面的查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    }

这一次获得MappedStatement ms 如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVkJL02K-1650516052833)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/bbd0c079418e440b6bef01551fadbd4f)]

关键查询
MybatisCachingExecutor
@Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //组装sql--我们主要研究这里的组装
        BoundSql boundSql = ms.getBoundSql(parameterObject);
		//获取一级缓存key
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        //从缓存查询还是直接查询数据库等
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
	
MappedStatement
	public BoundSql getBoundSql(Object parameterObject) {
    //sqlSource 为 DynamicSqlSource(动态sql拼接)见上图
	//SqlSouce里面已经解析mapper对应的sql
	//已经被解析为MixedSqlNode
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    ....
    return boundSql;
  }
<select id="selectSellReconciliationByPage"
            resultType="com.jdh.trade.model.bankcontribution.resp.QuerySellReconciliationResp">
        SELECT
        ...
        WHERE sr.root_code = po.root_code
        ...
        <if test="param.pushStatus != null and param.pushStatus !='' ">
            and sr.push_status = #{param.pushStatus}
select>
解析sql过程
@Override
  public BoundSql getBoundSql(Object parameterObject) {
     //下面有实体属性
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //处理一个个的sqlNode 编译出一个完整的xml的sql
    //rootSqlNode是 MixedSqlNode 合并sql节点
    //${} 已经在静态节点赋值了 因为是直接静态判断${ 然后替换 会有sql注入风险
    rootSqlNode.apply(context);
     //创建sql信息解析器
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
     //获取入参类型
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
     //执行解析:将带有#{}和${}的sql语句进行解析,然后封装到StaticSqlSource中
      //比如and sr.push_status = #{param.pushStatus} 解析成   and sr.push_status = ?
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
     //将解析后的sql语句还有入参绑定到一起(封装到一个对象中,此时还没有将参数替换到SQL占位符?)
      //此时变为静态绑定 StaticSqlSource
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
sql解析为MixedSqlNode节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NjkqYKO0-1650516052833)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/6f3a80fa6d0587f8844927a0a4e36141)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WYiKs5H5-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/fd0edc96898cccdbdfdebc55531df78f)]

将每一个节点进行各自的解析 然后拼装xml的sql 到 sqlBuilder (其实是装入string属性)

我们来看一下 rootSqlNode.apply(context);

rootSqlNode.apply(context)

每个节点根据自己的规则判断是否组装到sqlBuilder
MixedSqlNode.apply
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wl8g1ejY-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/0fc7f8cea1ab54cc583068836c25b9c5)]

这里我们注意一下为什么IfSqlNode节点会失效呢
注意:为什么param.pushStatus != ‘’ 进入这个方法会返回false

 @Override
  public boolean apply(DynamicContext context) {
      //test = param.pushStatus != null and param.pushStatus != ''
      //context.getBindings() 有前端传的参数条件
      //判断符合条件就加入
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

调用ExpressionEvaluator.evaluateBoolean 进行判断

public boolean evaluateBoolean(String expression, Object parameterObject) {
    //问题出现在这里 value 返回了false
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value instanceof Boolean) {
      //从这里返回boolean值 false
      return (Boolean) value;
    }
    if (value instanceof Number) {
      return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
    }
    return value != null;
  }

再调用OgnlCache.getValue方法 获取校验结果

//表达式expression = param.pushStatus != null and param.pushStatus != ''
//root 入参
public static Object getValue(String expression, Object root) {
    try {
      //Mybatis底层校验使用Ognl语法
      Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
        //这里调用Ognl.getValue进行比较 
      return Ognl.getValue(parseExpression(expression), context, root);
    } catch (OgnlException e) {
      throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
    }
  }

parseExpression(expression) 会从缓存中读取Node 如果没有 去解析成特定类型的Node对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtSyNZzf-1650516052834)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/7f5a45ede1b4c446ca6c010177e944aa)]

 //OgnlCache
 private static Object parseExpression(String expression) throws OgnlException {
    Object node = expressionCache.get(expression);
    if (node == null) {
      node = Ognl.parseExpression(expression);
      expressionCache.put(expression, node);
    }
    return node;
  }

比如这里转化成 ASTAND
以下节点都是继承SimpleNode
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0pTTkrP2-1650516052835)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/79511546ca850b7612af95c39ef14591)]

Ognl.getValue(parseExpression(expression), context, root)获取最终结果

public static Object getValue(Object tree, Map context, Object root)
            throws OgnlException
    {
        return getValue(tree, context, root, null);
    }
	
/**
     * 计算给定的OGNL表达式树,从给定的根对象中提取值。通过addDefaultContext()为给定的上下文和根设置默认上下文。
     *
     * @param tree
     *            要计算的OGNL表达式树,由parseExpression()返回
     			如:(param.pushStatus != null) && (param.pushStatus != "")
     * @param context
     *            求值的命名上下文
     * @param root
     *            OGNL表达式的根对象
     
     * @return 	  返回计算表达式的结果
     */
public static Object getValue(Object tree, Map context, Object root, Class resultType) throws OgnlException {
        OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);
        //根据解析表达式获取的节点 该例子为 ASTAND
    	Node node = (Node)tree;
        Object result;
        if (node.getAccessor() != null) {
            result = node.getAccessor().get(ognlContext, root);
        } else {
            //调用父类SimpleNode的getValue方法
			//该节点为 ASTAND
            result = node.getValue(ognlContext, root);
        }
		...
        return result;
    }
ASTAND节点树进行解析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uv5gijjR-1650516052836)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/4553a1d57700a78d5d412db1f5a5a389)]

调用父类 SimpleNode. getValue
以下两个方法方法为父类方法

public final Object getValue(OgnlContext context, Object source) throws OgnlException {
        Object result = null;
        if (context.getTraceEvaluations()) {
            ...
        } else {
            //获取常量值
            result = this.evaluateGetValueBody(context, source);
        }
        return result;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gCaqx2zA-1650516052836)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/b89e9fd510e869daf43bccaf08a6ac61)]

调用子类自己的判断实现getValueBody 从节点树叶子节点ASTConst一层一层根据规则返回结果给上一层

ASTAnd
 protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object result = null;
        int last = this._children.length - 1;

        for(int i = 0; i <= last; ++i) {
            result = this._children[i].getValue(context, source);
            if (i != last && !OgnlOps.booleanValue(result)) {
                break;
            }
        }

        return result;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4tnU2yD-1650516052837)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/eb2e2e018bc33c8ee5b76a11252e2fb1)]

ASTNotEq
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object v1 = this._children[0].getValue(context, source);
        Object v2 = this._children[1].getValue(context, source);
        return OgnlOps.equal(v1, v2) ? Boolean.FALSE : Boolean.TRUE;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HyFxeqay-1650516052837)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/6ada714321b6458785e31fabe2887a86)]

这里出现了问题为什么 0 和 ‘’ equal会相等

public static boolean equal(Object v1, Object v2) {
        if (v1 == null) {
            return v2 == null;
            //!isEqual 判断出问题
        } else if (v1 != v2 && !isEqual(v1, v2)) {
            if (v1 instanceof Number && v2 instanceof Number) {
                return ((Number)v1).doubleValue() == ((Number)v2).doubleValue();
            } else {
                return false;
            }
        } else {
            return true;
        }
    }

!isEqual(v1, v2) 判断成了相等返回true

public static boolean isEqual(Object object1, Object object2) {
        ...
				 {
                    //前面根据Ognl的算法
                    result = compareWithConversion(object1, object2) == 0;
                }


        return result;
    }
	public static int compareWithConversion(Object v1, Object v2) {
		...
        double dv1 = doubleValue(v1);
        double dv2 = doubleValue(v2);
        return dv1 == dv2 ? 0 : (dv1 < dv2 ? -1 : 1);
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tP6PMJ3z-1650516052838)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/d51079b0f256d5537fae3039142a2542)]

4、问题根源
public static double doubleValue(Object value) throws NumberFormatException {
        if (value == null) {
            return 0.0D;
        } else {
            Class c = value.getClass();
            if (c.getSuperclass() == Number.class) {
                return ((Number)value).doubleValue();
            } else if (c == Boolean.class) {
                return (Boolean)value ? 1.0D : 0.0D;
            } else if (c == Character.class) {
                return (double)(Character)value;
            } else {
                //这里将''改成了0.0D
                String s = stringValue(value, true);
                return s.length() == 0 ? 0.0D : Double.parseDouble(s);
            }
        }
    }

最后是 “” 转换成了0.0 去比较了

ASTChain
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object result = source;
        int i = 0;
        for(int ilast = this._children.length - 1; i <= ilast; ++i) {
            boolean handled = false;
            if (i < ilast && this._children[i] instanceof ASTProperty) {
                ASTProperty propertyNode = (ASTProperty)this._children[i];
                int indexType = propertyNode.getIndexedPropertyType(context, result);
				...
                }
            }

            if (!handled) {
			//将最后的链值返回
                result = this._children[i].getValue(context, result);
            }
        }
        return result;
    }
ASTProperty
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        //通过ASTConst 直接获取返回值 如 param 或 pushStatus 再读取入参对应的值
   		Object property = this.getProperty(context, source);
    	//根据 param 或 pushStatus 返回对应的值
        Object result = OgnlRuntime.getProperty(context, source, property);
        if (result == null) {
            result = OgnlRuntime.getNullHandler(OgnlRuntime.getTargetClass(source)).nullPropertyValue(context, source, property);
        }

        return result;
    }
ASTConst
protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        return this.value;
    }
解决

(1) 不用Integer接收,使用String类型接收

(2)去掉【参数!=’‘】 的非空判断
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sex5LBqb-1650516052838)(http://10.0.17.20/server/index.php?s=/api/attachment/visitFile/sign/e872d96804f4039fa5878d896d0f970f)]

番外:如果String类型需要判断!=0,则需要写成 xxx != ‘0’.toString()

你可能感兴趣的:(spring,mybaits,java,java)