一、前言
在mybatis实际开发中遇到这样一个场景,foreach标签中需要动态生成一个表名,如t0,t1,t2…等等, 可以在dao层传入,也可以在foreach中用bind标签生成,这里我们介绍使用bind生成该变量。
示例如下:
dao层传入[张三、李四]。 mapper.xml中根据传入的列表个数使用foreach进行union all拼接,并且需要动态生成表名 t0、t1等等。最终拼接sql如下:
select * from(
select * from user where name='张三' order by id
) t0
union all
select * from(
select * from user where name='李四' order by id
) t1
二、解决方法/foreach中使用bind
下面为公共的UserService.java、UserMapper.java中的代码
UserService.java代码:
@Autowired
private UserMapper userMapper;
public List<User> selectByNameList(){
userMapper.selectByNameList(Arrays.asList("张三","李四"));
return null;
}
UserMapper.java代码:
public interface UserMapper {
List<User> selectByNameList(List<String> nameList);
}
2.1、方法一:#传参
UserMapper.xml代码
<select id="selectByNameList" resultType="com.demo.entity.User"
parameterType="java.util.List">
<foreach collection="list" item="item" separator="union all" index="index">
select * from (
select * from user where name = #{item} order by id
) t#{index}
</foreach>
</select>
日志如下:
==> Preparing: select * from ( select * from user where name = ? order by id ) t? union all select * from ( select * from user where name = ? order by id ) t?
==> Parameters: 张三(String), 0(Integer), 李四(String), 1(Integer)
2.2、方法二:$传参 bind生成变量
UserMapper.xml代码
<select id="selectByNameList" resultType="com.demo.entity.User"
parameterType="java.util.List">
<foreach collection="list" item="item" separator="union all" index="index">
<bind name="tableName" value="'t' + index"></bind>
select * from (
select * from user where name = #{item} order by id
) ${tableName}
</foreach>
</select>
日志如下:
==> Preparing: select * from ( select * from user where name = ? order by id ) t0 union all select * from ( select * from user where name = ? order by id ) t1
==> Parameters: 张三(String), 李四(String)
注意:
${}
方式传参有sql注入问题,所以确保${}
中的变量没有注入风险。
三、bind变量覆盖问题/错误示例
上面的示例中,我们在foreach标签内使用了bind绑定变量给tableName并且用${tableName}
的方式获取值。如果我们是使用#{tableName}
的方式获取值,会发现每次tableName的值都会是最后一个元素。
错误示例一:
<select id="selectByNameList" resultType="com.demo.entity.User"
parameterType="java.util.List">
<foreach collection="list" item="item" separator="union all" index="index">
<bind name="tableName" value="'t' + index"></bind>
select * from (
select * from user where name = #{item} order by id
) #{tableName}
</foreach>
</select>
日志文件:
==> Preparing: select * from ( select * from user where name = ? order by id ) ? union all select * from ( select * from user where name = ? order by id ) ?
==> Parameters: 张三(String), t1(String), 李四(String), t1(String)
错误示例二:循环内重复绑定同名变量
<foreach item="item" collection="list" separator=",">
<!-- 错误:每次循环覆盖 previousItem,最终所有值相同 -->
<bind name="previousItem" value="item" />
#{previousItem}
</foreach>
四、总结
总结:在 MyBatis 的 foreach 循环中,应避免使用 bind 来创建每个迭代的临时变量,因为 bind 的作用域是当前上下文,在循环中会被覆盖。替代方案是在 Java 代码中预处理数据,或者直接在表达式中使用循环项和索引。
在 MyBatis 的 foreach 循环中使用 bind 元素时,需特别注意 bind 的作用域是当前整个 SQL 语句,而非单次循环迭代。这意味着在循环内多次使用 bind 绑定同名变量时,后一次会覆盖前一次的值,可能导致逻辑错误。