MyBatis中仅#{}实现参数化绑定,${}为纯字符串替换,无法预编译。动态生成的SQL片段中无法强制参数化,需将#{}写在XML解析的SQL上下文内。动态表名等元数据只能通过白名单校验控制,@SelectProvider提供更可控的替代方案。
MyBatis动态生成XML强制SQL参数化绑定
作者:技术拾光
时间:2026-06-23
浏览:297
文章目录
在 MyBatis 中,
核心结论:MyBatis 不允许在动态生成的 XML 中“强制”使用参数化绑定。因为
常见误区是将整条 SQL 字符串放入 Map,再用 真正安全的参数化:让
要实现 PreparedStatement 绑定,
#{} 是唯一可靠的参数化绑定方式,而 ${} 本质上是字符串拼接,无法实现预编译。尤其在动态生成的 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 动态标签内部(如 )才有效
- 传入的字符串 "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
容易被忽略的关键点:动态生成 SQL 的正确含义
所谓“动态生成 SQL”,并非在运行时拼字符串,而是 MyBatis 在执行 SQL 前,根据传入的参数条件,利用内置标签(、、)实时生成最终 SQL 文本。这个过程完全可控、可审计,且 #{} 一定生效。一旦跳出这个机制,就只能依靠严格的编码规范和人工校验来保障安全。实用工具推荐
电子包浆
使用 ›绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
图片压缩
使用 ›选择画质和输出格式,预览压缩前后的大小。
相关文章