MyBatis动态生成XML强制SQL参数化绑定

作者:技术拾光

时间:2026-06-23

浏览:297

MyBatis中仅#{}实现参数化绑定,${}为纯字符串替换,无法预编译。动态生成的SQL片段中无法强制参数化,需将#{}写在XML解析的SQL上下文内。动态表名等元数据只能通过白名单校验控制,@SelectProvider提供更可控的替代方案。

文章目录
在 MyBatis 中,#{} 是唯一可靠的参数化绑定方式,而 ${} 本质上是字符串拼接,无法实现预编译。尤其在动态生成的 SQL 上下文中,想要“强制”参数化绑定并非不可能,但前提是必须彻底理解 MyBatis 的解析机制。 MyBatis动态生成XML强制SQL参数化绑定 核心结论:MyBatis 不允许在动态生成的 XML 中“强制”使用参数化绑定。因为 ${} 属于纯字符串替换,不经过 PreparedStatement;只有 #{} 能实现参数化,且仅作用于值——表名、列名、排序字段等元数据需要另外处理。

${sql} 看似参数化,实际存在严重风险

常见误区是将整条 SQL 字符串放入 Map,再用 ${sql} 插入:
这种写法表面简洁灵活,但若 sql 值来自用户输入(例如前端传入 "select * from user where name = 'admin' -- "),数据库将直接执行任意 SQL。MyBatis 不会对 ${} 做任何转义或预编译,它仅是赤裸裸的替换。 - ${} 在 XML 解析阶段直接完成字符串替换,与 Java 的 String.format() 无异 - 即使在 Map 中给 code 字段写上 #{code},它也不会被解析——#{} 只在 MyBatis 动态标签内部(如 )才有效 - 传入的字符串 "select count(*) from user where code like #{code}" 中的 #{code} 是纯文本,MyBatis 不会将其视为参数占位符

真正安全的参数化:让 #{} 出现在 MyBatis 解析的 SQL 上下文中

要实现 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> listUsers(@Param("table") String table, @Param("status") int status);

容易被忽略的关键点:动态生成 SQL 的正确含义

所谓“动态生成 SQL”,并非在运行时拼字符串,而是 MyBatis 在执行 SQL 前,根据传入的参数条件,利用内置标签(、、)实时生成最终 SQL 文本。这个过程完全可控、可审计,且 #{} 一定生效。一旦跳出这个机制,就只能依靠严格的编码规范和人工校验来保障安全。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章