← 返回题目列表

MyBatis 中 #{} 和 ${} 有什么区别?

高频 简单 第 1 / 24 题 更新于 2026/07/26
MyBatis参数占位符SQL 注入PreparedStatement

简化版

#{} 会生成 JDBC ? 占位符,由 PreparedStatement 和 TypeHandler 安全绑定值;${} 是把文本直接拼进 SQL,不做参数化或转义。普通业务值一律优先 #{},表名、列名、排序方向等必须动态拼接时只能使用受控白名单,绝不能直接接收用户输入。

详细版

WHERE id = #{id} 生成 WHERE id = ?,SQL 结构与参数值分离,可复用执行计划并显著降低注入风险。ORDER BY ${column} 会先把 column 文本替换进 SQL,攻击者可注入额外表达式。

占位符不能替代表名、列名和 SQL 关键字,因为数据库不会把 ORDER BY ? 的参数值当列标识符解析。正确做法是在 Java 或 <choose> 中把外部枚举映射为固定 SQL 片段;模糊查询用参数值拼好 % 或数据库拼接函数,IN 列表用 <foreach>

完整版教学

一、两者生成 SQL 的时机不同

<select id="find" resultType="User">
  SELECT * FROM users WHERE name = #{name}
</select>

最终发送给 JDBC 的 SQL 类似 name = ?,参数单独绑定。若改成 ${name},值会直接进入 SQL 文本,字符串引号也要自己处理,并暴露注入风险。

写法生成方式是否参数化适合内容
#{name}生成 ?,通过 PreparedStatement 绑定普通值:字符串、数字、时间、枚举值
${column}文本替换进 SQL受控白名单中的表名、列名、排序方向
<foreach> #{id}多个 ? + 多个参数映射IN 列表、批量 values

记忆钩子:#{} 是“值通道”,${} 是“结构通道”。值可以参数化,表名列名这种 SQL 结构只能白名单拼接。

二、为什么参数化更安全

PreparedStatement 把 SQL 结构和数据分开,输入中的引号、注释符等只作为值处理。MyBatis 再根据 Java 类型和 jdbcType 选择 TypeHandler 调用合适的 set 方法。

#{} 不是对字符串做简单转义,而是使用 JDBC 参数绑定;这也是它比手工拼接可靠的根本原因。

输入 name = "' OR 1=1 --"

使用 #{}:
SQL: SELECT * FROM users WHERE name = ?
参数: "' OR 1=1 --"
含义: 查 name 等于这段字符串的用户

使用 ${}:
SQL: SELECT * FROM users WHERE name = '' OR 1=1 --'
含义: SQL 结构被改变,可能变成全表条件

这个例子说明,安全差异不在于 MyBatis 帮你“转义得更好”,而在于数据库从一开始就知道哪部分是 SQL 结构、哪部分只是参数值。

三、动态排序应该怎么写

错误写法:

ORDER BY ${sortBy} ${direction}

安全写法是把外部值转换成内部枚举,再用固定分支:

<choose>
  <when test="sortBy == 'createdAt'">ORDER BY created_at</when>
  <when test="sortBy == 'name'">ORDER BY name</when>
  <otherwise>ORDER BY id</otherwise>
</choose>

排序方向也只允许 ASC/DESC 两种固定片段。白名单校验必须发生在拼接前,不能靠过滤几个危险字符。

四、LIKE 与 IN 的正确处理

LIKE 可在 Java 中把参数构造成 %keyword% 后用 #{keyword},或使用数据库的字符串连接函数。不要写 LIKE '%${keyword}%'

IN 查询应使用 foreach 为每个元素生成一个参数占位符,而不是把逗号字符串整体塞给一个 #{ids},也不要用 ${ids} 直接拼接。

五、#{} 的常用属性

可以声明 jdbcTypejavaTypetypeHandler 等参数映射选项。尤其给可能为 null 的值绑定到某些数据库时,显式 jdbcType 可避免驱动无法判断类型。

这些配置只影响值如何绑定,不会让占位符变成动态标识符。

六、常见误区与追问

  • 误区:${} 也会像 #{} 一样做安全转义。 ${} 是文本替换,不做 JDBC 参数化,用户输入直接进入 SQL 结构就有注入风险。
  • 误区:所有动态内容都能用 #{} 表名、列名、排序方向属于 SQL 结构,数据库不会把 ? 当标识符解析,必须从白名单选择固定片段。
  • 追问:模糊查询应该怎么写? 在 Java 里拼好 %keyword% 后用 #{keyword},或用数据库字符串连接函数,避免 LIKE '%${keyword}%'
  • 追问:IN 查询为什么不用一个 #{ids} 一个占位符只代表一个值,多个 id 应用 <foreach> 生成多个 ? 和参数映射。
  • 误区:过滤危险字符就能安全使用 ${} 黑名单过滤容易漏掉编码、注释、函数等绕过方式,正确做法是固定白名单。
  • 追问:jdbcType 的作用是什么? 它告诉 JDBC 空值或特殊类型如何绑定,只影响参数值绑定,不改变 SQL 结构。

七、加强记忆

#{} 是“SQL 留 ?、值单独绑定”,${} 是“文本原样拼进去”。值永远优先 #{};只有数据库必须解析成语法结构的表名、列名、排序方向才考虑固定白名单片段,任何用户原文都不能直接进入 ${}