MyBatis 中 #{} 与 ${} 有什么区别?会引发 SQL 注入吗?

结论先行:一句话区别——#{} 是预编译占位符,MyBatis 会把它渲染成 PreparedStatement 的 ?,参数值由 JDBC 驱动转义,能有效防止 SQL 注入;${} 是字符串直接拼接,把值原样拼进 SQL,灵活但危险。能用 #{} 的场合绝不用 ${};只有表名、列名、ORDER BY 字段这类“SQL 结构”必须动态化时才用 ${},并且必须做白名单校验。

对比表

维度#{}${}
处理方式预编译 ? 占位字符串直接拼接
安全性防 SQL 注入存在注入风险
使用场景条件值、批量参数表名/列名/排序字段/动态片段
引号处理自动按类型转义原样输出、不补引号
执行计划同语句可复用每句都是新 SQL

正反示例

<!-- 安全写法:值用 #{},走预编译占位 -->
<select id="queryByName" resultType="User">
  SELECT * FROM user
  WHERE name LIKE CONCAT('%', #{kw}, '%')
</select>

<!-- 危险写法:用户输入直接拼入 SQL -->
<!-- 若 kw = x' OR '1'='1,条件恒真 → 全表数据被带出 -->
<select id="badQuery" resultType="User">
  SELECT * FROM user WHERE name LIKE '%${kw}%'
</select>

何时必须用 ${} 以及怎么防护

  • 动态表名(如按月分表)、动态排序字段无法用占位符表达,只能拼接;
  • 防护方案:把允许值收进白名单/枚举校验后再拼,绝不直接信任外部入参。

常见追问 / 记忆点

  • 追问:#{} 既然安全,为什么 MyBatis 日志里看起来是完整 SQL?答:MyBatis 先渲染出带 ? 的 SQL,参数由 PreparedStatement 单独传入,用户的“值”不会成为 SQL 文本的一部分。
  • 追问:ORDER BY 的字段名能用 #{} 吗?答:不能,#{} 只会作为值传入;排序字段属于 SQL 结构,只能用 ${} 配合白名单。
  • 记忆点:#{} 管“值”,${} 管“结构字面量”;默认 #{},非用 ${} 必校验。
笔记加载中…