{}能防sql注入因其触发preparedstatement预编译,参数安全绑定;${}是字符串拼接,直接替换导致注入。仅当动态sql结构(如表名、排序字段)必须用${},须白名单校验或正则过滤。

为什么#{}能防SQL注入而${}不能
根本原因在于 MyBatis 对两者的处理时机和方式完全不同:#{} 触发预编译(PreparedStatement),参数被当作占位符传入,数据库驱动自动做类型转换和转义;${} 是纯字符串替换,MyBatis 在 SQL 解析阶段就直接把变量值拼进 SQL 字符串里,不经过 JDBC 预编译流程。
比如传入用户名 "admin' OR '1'='1":
-
WHERE username = #{username}→ 最终执行的是WHERE username = ?,参数以安全方式绑定,不会改变 SQL 结构 -
WHERE username = '${username}'→ 直接变成WHERE username = 'admin' OR '1'='1',语句逻辑已被篡改
什么场景必须用${},以及怎么降低风险
${} 唯一合理用途是动态拼接 **SQL 结构部分**,比如表名、字段名、ORDER BY 子句、UNION 查询等——这些无法用 #{} 替代,因为 PreparedStatement 不允许对表名或关键字做参数化。
使用时必须严格校验输入,常见做法:
立即学习“Java免费学习笔记(深入)”;
- 只接受白名单枚举值,例如排序字段限定为
"id"、"create_time"、"status" - 用正则过滤:仅允许字母、数字、下划线,且长度限制在 32 字以内
- 避免从用户请求中直接取值,优先通过接口参数映射到内部枚举或配置项
错误示例:
SELECT * FROM ${tableName} WHERE id = #{id} —— 若 tableName 来自 HTTP 参数且未校验,可导致库表遍历攻击。ORDER BY 中用${}的典型写法与陷阱
这是最常踩坑的地方。很多人写成:ORDER BY ${sortField} ${sortOrder},但若 sortOrder 是用户传的 "ASC; DROP TABLE user;",就可能触发多语句执行(取决于数据库是否开启 allowMultiQueries)。
安全做法是分别约束两个变量:
-
sortField:匹配^[a-zA-Z_][a-zA-Z0-9_]{0,31}$ -
sortOrder:只允许"ASC"或"DESC"(建议转大写后比对)
推荐写法:
<if test="sortField != null and sortField != ''">
ORDER BY ${sortField}
<choose>
<when test="sortOrder == 'DESC'">DESC</when>
<otherwise>ASC</otherwise>
</choose>
</if>
性能与日志表现差异
虽然不影响功能,但会影响排查效率:#{} 在 MyBatis 日志中显示为带问号的 SQL(如 SELECT * FROM user WHERE id = ?),真实参数单独打印;${} 则直接显示拼接后的完整 SQL(如 SELECT * FROM user_log WHERE type = 'login')。
这意味着:
- 用
${}时,如果 SQL 报错,你看到的就是最终执行语句,容易定位问题 - 但日志里也暴露了原始参数值,敏感字段(如手机号、身份证)可能泄露
-
#{}日志更“干净”,但调试时需对照参数日志一起看
别为了省事把所有地方都换成 ${}——看似方便,实则埋下注入和合规隐患。










