MyBatis SQL片段参数未正确替换问题咨询
问题描述
编写了如下MyBatis SQL片段:
<sql id="day"> <choose> <when test="${property} == 'day'"> substring(datachange_createtime, 1, 10) </when> <otherwise> ${property} </otherwise> </choose> </sql>
并通过如下方式引用该片段:
select * from my_table <if test="groupBy != null"> group by <foreach collection="groupBy" item="groupByAttr" separator=","> <include refid="day"> <property name="property" value="groupByAttr"/> </include> </foreach> </if>
执行时出现异常:MyBatis未将property替换为实际参数,而是直接输出groupByAttr,生成的SQL为:
select * from my_table group by groupByAttr
若将otherwise分支中的${property}改为#{${property}},会生成预编译SQL:
select * from my_table group by ?
虽结果正确但不需要预编译形式。怀疑test与otherwise分支的参数替换行为存在差异,请问这是Bug还是使用错误?该如何解决?
原因分析
这不是MyBatis的Bug,是对<include>标签的property替换逻辑理解有误:
<include>的property属于静态文本替换,在SQL解析的早期阶段执行,此时foreach的item变量(groupByAttr)还未被解析为实际的字段值,所以${property}会被直接替换成字符串groupByAttr。- 而
<when>标签的test属性是OGNL表达式,在运行时(参数传入后)才解析:此时${property}先被替换成字符串groupByAttr,OGNL再从当前上下文取出groupByAttr变量的实际值进行判断,所以test分支能正常工作。
解决方法
提供两种可行方案:
方案1:逻辑内联(推荐,代码更直观)
直接将<sql>片段的逻辑移到foreach内部,避免<include>的静态替换限制:
select * from my_table <if test="groupBy != null"> group by <foreach collection="groupBy" item="groupByAttr" separator=","> <choose> <when test="groupByAttr == 'day'"> substring(datachange_createtime, 1, 10) </when> <otherwise> ${groupByAttr} </otherwise> </choose> </foreach> </if>
方案2:保留<sql>片段复用
修改<sql>片段,通过OGNL动态解析变量,让property的值作为变量名从上下文获取实际字段:
<sql id="day"> <choose> <when test="_parameter[property] == 'day'"> substring(datachange_createtime, 1, 10) </when> <otherwise> ${_parameter[property]} </otherwise> </choose> </sql>
引用方式保持不变即可,这样${_parameter[property]}会先解析property的值(即groupByAttr),再从当前参数上下文取出该变量对应的实际字段名,生成正确的非预编译SQL。
内容的提问来源于stack exchange,提问作者Expressway Retrograding
相关产品推荐
相关产品推荐

