#{}通过预编译将参数转为?占位符,SQL先编译、参数后填充,数据库严格区分“代码”与“数据”,恶意输入如admin' OR '1'='1仅被当字符串处理;${}是Java层直接字符串拼接,SQL未编译即含用户输入,该输入参与语法解析,导致OR '1'='1等被当作逻辑执行,引发注入。

面试时回答这个问题,重点不是背定义,而是讲清“为什么安全”和“为什么危险”,用技术逻辑说服面试官。
#{} 为什么能防 SQL 注入?关键在预编译
MyBatis 遇到 #{} 会把整个表达式替换成 JDBC 的 ? 占位符,SQL 语句提前交给数据库编译(语法解析 + 执行计划生成),参数值在执行阶段才由 PreparedStatement 安全填充。数据库会严格区分“代码”和“数据”——参数值永远被当字符串/数字处理,不会参与 SQL 解析。
比如传入 "admin' OR '1'='1":
- 最终发给 MySQL 的是:
SELECT * FROM user WHERE username = ? - 参数单独传入:
["admin' OR '1'='1"] - 数据库只查用户名等于这个完整字符串的记录,OR 条件根本没被当作 SQL 逻辑执行
${} 为什么会导致 SQL 注入?本质是字符串拼接
${} 在 MyBatis 层就直接把变量值拼进 SQL 字符串里,再整体发给数据库。数据库收到的已经是“成品 SQL”,所有内容都参与语法解析。
立即学习“Java免费学习笔记(深入)”;
同样传入 "admin' OR '1'='1":
- 拼接后变成:
SELECT * FROM user WHERE username = 'admin' OR '1'='1' - MySQL 解析时,
'1'='1'是永真表达式,结果就是查出全部用户 - 如果用于 DELETE 或 UPDATE,后果更严重——比如
DELETE FROM user WHERE id = ${id},传入"1 OR 1=1"就可能删光整张表
哪些场景必须用 ${}?怎么保安全?
只有三类无法用 #{} 的语法级动态需求才允许用 ${},且必须加白名单校验:
-
排序字段:ORDER BY 后不能是 ?,只能是列名,如
ORDER BY ${sortField} ${sortOrder};传参时只接受"id"、"name"、"asc"、"desc"等固定值 -
表名或库名:分库分表场景下,如
SELECT * FROM ${tableName};表名必须来自配置或枚举,禁止用户输入 -
动态列名:如统计查询中的
SUM(${metricColumn});列名需从预设字段列表中匹配
任何涉及用户输入的场景,一律禁用 ${} —— 模糊查询用 #{} + Java 拼好 "%xxx%",IN 查询用 <foreach> 标签,都不是 ${} 的理由。
一句话总结区别
#{} 是让数据库“先编译模板、后填数据”,安全可复用;${} 是让 Java “先拼 SQL、再扔给数据库”,灵活但高危,仅限非用户可控的静态语法片段。


















