MyBatis 防 SQL 注入靠的是 #{} 预编译机制,而非 CONCAT 等函数;CONCAT 仅是字符串拼接工具,若配合 ${} 使用仍会注入;安全做法是所有用户输入均用 #{param},动态字段名等必须白名单校验后才可用 ${}。

MyBatis 中不能靠 CONCAT 函数防 SQL 注入,它本身不提供防护能力;真正起作用的是 MyBatis 的预编译参数绑定机制(#{}),而非字符串拼接函数。
CONCAT 函数只是 SQL 拼接工具,不是安全屏障
CONCAT 是数据库层面的字符串连接函数(如 MySQL 的 CONCAT(a, b)),它运行在 SQL 执行阶段,和参数是否安全无关。如果用 ${} 动态拼接、再套 CONCAT,照样会注入。
- ❌ 危险写法(仍可注入):
WHERE name = CONCAT('${prefix}', #{name})
若prefix来自用户输入且用${},攻击者传入prefix=abc' OR 1=1 --就破防。 - ✅ 安全写法(依赖预编译):
WHERE name = CONCAT(#{prefix}, #{name})
此时#{prefix}和#{name}都被当作 PreparedStatement 参数处理,数据库自动转义,不会参与 SQL 解析。
防注入的核心:坚持用 #{},禁用 ${}
MyBatis 的 SQL 注入防护完全建立在 JDBC 的 PreparedStatement 基础上。#{} 触发参数占位符(?),由驱动做类型安全绑定;${} 是纯文本替换,等同于手动拼 SQL,必须严格规避。
- 所有用户输入(包括字段名、表名、排序方向等动态部分)——若真需动态,应白名单校验后才用
${},绝不可直接透传 - 字符串值、数字、日期等常规参数 —— 统一用
#{param},MyBatis 自动处理类型和引号 - CONCAT、
||、+等连接操作 —— 只要所有参与项都来自#{},就是安全的
需要拼接字符串?优先用 Java 层处理
除非业务强依赖数据库函数(如需要利用 MySQL 的 CONCAT + COLLATE 排序),否则建议在 Java 代码中完成字符串组合,再整体传入。
立即学习“Java免费学习笔记(深入)”;
- 例如:
Java:String fullName = prefix + name;
XML:WHERE name = #{fullName} - 好处:逻辑清晰、便于单元测试、避免数据库函数差异(如 Oracle 用
||,SQL Server 用+) - 缺点:丧失数据库侧的部分优化能力(如索引合并),但对绝大多数场景影响极小
特殊情况:动态列名或表名怎么办?
这类无法用 #{} 的场景,必须自己把关:
- 定义明确的枚举或静态常量列表(如
enum OrderField { NAME, AGE, CREATETIME }) - 接收前端参数后,用
Enum.valueOf()或Map.containsKey()校验合法性 - 确认通过后再用
${fieldName}拼入 SQL —— 此时风险可控,因已排除非法输入 - 绝对禁止将用户任意输入(如 HTTP 参数 raw=xxx)不经校验就塞进
${}


















