先说一个核心判断:在 MyBatis 中,#{} 是唯一可靠的参数化绑定方式,而 ${} 本质上仍是字符串拼接,无法实现预编译保护。尤其是在动态生成的 SQL 上下文里,想要“强制”参数化?确实可以做到,但前提是必须彻底理解 MyBatis 的解析机制——

结论明确:MyBatis 不允许你“强制”在动态生成的 XML 中使用参数化绑定。因为 ${} 并非参数化,它只是纯粹的字符串替换,不走 PreparedStatement。真正能实现参数化的只有 #{},而且它仅适用于值占位——表名、列名、排序字段这些元数据,必须另寻他法。
为什么 ${sql} 看似参数化,实则是个大坑
常见的误区,是将整条 SQL 字符串塞进一个 Map,然后用 ${sql} 插入:
这种写法表面简单灵活,但你想想,只要 sql 的值来自用户输入——比如前端传一个 "select * from user where name = 'admin' -- "——数据库就直接执行你给的任意 SQL 了。MyBatis 不会对 ${} 做任何转义或预编译,它就是赤裸裸的替换。
${}在 XML 解析阶段直接完成字符串替换,和 Java 的String.format()没有本质区别- 即便你在 Map 里给
code字段写上了#{code},它也不会被解析——因为#{}只在 MyBatis 动态标签(比如)内部、由 MyBatis 自己解析的 SQL 片段里才有效 - 你传进去的
"select count(*) from user where code like #{code}"这个字符串,里面的#{code}就是纯文本,MyBatis 根本不会把它当成参数占位符
真正安全的参数化:让 #{} 出现在 MyBatis 解析的 SQL 上下文里
想让 MyBatis 对参数做 PreparedStatement 绑定,#{} 必须老老实实写在 XML 的静态或动态 SQL 片段里,不能在运行时拼进字符串。一句话:结构固定,值可变,全用 #{};结构需要动态,用 等标签控制;千万别把带 #{} 的字符串当值传进来,指望它被二次解析——那是不可能的。
举个例子:
动态表名或列名?那就别指望参数化了
表名、排序字段、GROUP BY 列……这些属于 SQL 元数据,无法用 #{} 参数化。此时只能依靠白名单校验和一系列显式限制来兜底,而不是幻想“强制参数化”能解决一切。
- 在 Java 层严格校验输入:比如
tableName只允许是"user"、"order"、"product"这些预设值 - 用
${}拼接前,先通过Enum或Set做白名单匹配,不匹配直接抛异常 - 不要直接从 HTTP 参数、JSON body 取出来就拼 SQL,优先用固定枚举或配置驱动
- 如果要支持任意列排序,至少拆成两层:
sortField(白名单校验) +sortOrder(只允许"ASC"/"DESC")
比如这样:
Provider 类:更可控的替代方案
如果你觉得 XML 动态 SQL 越写越绕,又不想裸用 ${},那 @SelectProvider 是更清晰的选择。它的思路是:把 SQL 构建逻辑单独抽到 Java 方法里,你能完全控制拼接过程,并在拼接时手动做白名单检查。
- SQL 字符串由 Java 方法返回,MyBatis 仍然只认其中的
#{}作为参数占位符 - 你可以用
StringBuilder拼接表名、列名,但必须自己校验合法性 - 参数值仍然是走
#{}的,保证 PreparedStatement 绑定
代码示例:
public class UserSqlProvider { public String listUsers(Map params) { String table = (String) params.get("table"); if (!Arrays.asList("user", "admin_user").contains(table)) { throw new IllegalArgumentException("Invalid table: " + table); } return "SELECT * FROM " + table + " WHERE status = #{status}"; }}// 接口上@SelectProvider(type = UserSqlProvider.class, method = "listUsers")List
最后,也是最容易被忽略的一点:所谓“动态生成 SQL”,不是指在运行时拼字符串,而是指 MyBatis 在执行 SQL 前,根据传入的参数条件,用内置标签(、、)实时生成最终的 SQL 文本。这个过程是完全可控、可审计的,而且 #{} 一定生效。一旦跳出这个机制,就只能靠严格的编码规范和人工校验来兜底了。
