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,不建议使用覆盖。建议定义新的参数名,避免有坑。