mybatis bind 标签 覆盖 复杂对象的某个属性值 问题。

需求: 有四个sql 都需要用一个 相同的where 条件,于是定义了一个sql 标签。 然后在每个sql中使用 

<include refid="myWhereSql"></include> 引入。

后来需求变更,有两个sql 的where条件有一个参数需要和其他两个不同。 于是想到了bind标签

sql 3 : <bind name = "req.cc"  value="1">

sql 4 : <bind name = "req.cc"  value="2">

List<O1> m1(@Param("req") CRequest req);  cc 为 CRequest 中的一个 属性。

这样在sql3 和 sql4 中 cc的参数就可以 不同了 。 

<if test="req.cc!= null and req.cc!= ''">
and t.col1= #{req.cc}
</if>
<if test="req.dd!= null and req.dd!= ''">
and t.col2 = #{req.dd}
</if>

发现 #{req.cc} 是能正确取到值的。 于是以为解决问题了。 然而后来发现 参数dd我 传值了,但是 #{req.dd} 却取不到。

研究源码发现: MybatisDefaultParameterHandler.setParameters

                    if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {

重点是 hasAdditionalParameter 方法: 

public boolean hasAdditionalParameter(String name) {
    String paramName = new PropertyTokenizer(name).getName();
    return additionalParameters.containsKey(paramName);
  }
PropertyTokenizer 解析后的getName 返回是req (说明是按照.分割的)。 
所有req.cc 和 req.dd 都是 走这个 if 分支去获取value 值的。
value = boundSql.getAdditionalParameter(propertyName);

这里调试代码发现mybatis 会把参数生成两个map , 一个是 方法传递的参数,名称为 _paramters , 一个是额外的参数,比如bind的 名字是 req (本例) 。 

这就导致 cc 和  dd 都去 从这个 名称为req的map 中获取,所以dd 是没有值的。 

看来这就是一个前缀取法的坑,对于复杂对象,优先去前缀。由于先取前缀, 本来只是想覆盖其中一个属性的,这样一来,等于前缀下的所有属性都被覆盖了。 

 

那么如果我直接写 <bind name="cc" value = "1" > , debug 发现 他会 往 additionalParameters(注意他也是一个map) 中添加一个 key 为 cc 的 键。所以也不行。

 

想到一个解决方案: 既然是map , 那么能不能这样呢 ?

<bind name="_parameter.param1.cc" value="1"></bind> debug发现 cc 能取到覆盖后的值, dd 也能正常取值了。 

 

但是新的问题却出现了: 

<if test="req.cc!= null and req.cc!= ''">
    and t.col1= #{req.cc}
</if>

我把cc 设置为 null, 发现 if 判断并没有其作用。debug代码在 MybatisCachingExecutor.query 方法,然后调用MappedStatement.getBoundSql 生成 BoundSql 。其会根据 bind 标签配置 和 方法参数 共同解析生成sql 语句。

DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);

 

if 判断会调用 IfSqlNode.apply() , 该方法会解析if表达式, 

 

 

这里发现bind的 参数 名称尽然是 _parameter.req.cc ,而不是像处理参数那样覆盖_parameter这个map里面的req参数值。  

 

 

 

根据这个方法,其最终会找到 _parameter中找到req , 在req中找到 cc。  这个cc 是我方法请求参数,并没有被覆盖 , 显然不是null。

结论: mybatis xml 中 if 判断  和  取值(如#{cc})使用的参数处理机制不同,导致取值能覆盖,但是if 判断不会覆盖。这算不算bug。 

 

 

结论:

1. 不能用bind 覆盖复杂对象的的某个属性。 

2. 为避免bug,不建议使用覆盖。建议定义新的参数名,避免有坑。

posted on 2022-08-09 11:13  远方的人  阅读(441)  评论(0编辑  收藏  举报

导航