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

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

一句话总结

  • #{}:预编译参数占位符 (?),走 PreparedStatement 与 TypeHandler,防 SQL 注入,用于"值"。
  • ${}:字符串原样拼接 (文本替换),不做类型处理与转义,有 SQL 注入风险 ,仅用于"SQL 片段"(表名/列名/排序等),必须配合白名单。

1. 核心区别对比(背这一张表)

维度 #{} ${}
替换方式 生成 ? 占位符,参数通过 JDBC 绑定 直接把字符串拼进 SQL(文本替换)
底层实现 PreparedStatement + TypeHandler 字符串拼接
SQL 注入 低(参数不会当 SQL 执行) 高(输入可变成 SQL)
类型处理 有(按 Java/JDBC 类型设置参数) 无(你给啥拼啥)
适用对象 值:where 条件值、insert/update 的值、limit 值等 SQL 片段:表名、列名、排序字段、排序方向、分区名等
性能 预编译可复用执行计划(通常更好) SQL 文本变化大,缓存/复用更差

2. 底层原理:本质是"参数绑定" vs "文本替换"

2.1 #{}:参数绑定(PreparedStatement)

写法:

xml 复制代码
<select id="findUser" resultType="User">
  SELECT * FROM user WHERE name = #{name}
</select>

MyBatis 生成的 SQL(形态):

sql 复制代码
SELECT * FROM user WHERE name = ?

随后由 JDBC 绑定参数:

  • TypeHandler 决定如何把 Java 类型设置到 JDBC(setString/setInt/setTimestamp...)
  • 参数作为"值"传递,不会被当成 SQL 语法的一部分执行,因此天然抗注入

2.2 ${}:文本替换(拼接到 SQL)

写法:

xml 复制代码
<select id="findUsers" resultType="User">
  SELECT * FROM user ORDER BY ${sortField} ${sortOrder}
</select>

当 sortField=age、sortOrder=DESC 时,直接拼成:

sql 复制代码
SELECT * FROM user ORDER BY age DESC

这一步发生在 SQL 发送到数据库之前,MyBatis 不会对 ${} 做转义/类型处理。


3. SQL 注入风险:${} 真实危险点

如果用户可控输入被用于 ${}:

  • sortField = "age desc; drop table user; --"

可能拼成:

sql 复制代码
SELECT * FROM user ORDER BY age desc; drop table user; --  DESC

数据库是否允许多语句与驱动配置有关,但风险本质存在。

结论:

  • 值一律用 #{}
  • ${} 必须做白名单/枚举映射或用 MyBatis 动态标签替代

4. 典型使用场景(怎么用才对)

4.1 必须用 #{} 的场景(值)

  • WHERE id = #{id}
  • SET name = #{name}
  • INSERT INTO ... VALUES (#{v1}, #{v2})
  • LIMIT #{offset}, #{pageSize}(注意不同数据库方言)

4.2 可能用 ${} 的场景(SQL 标识符/片段)

  • 动态表名:FROM ${tableName}
  • 动态列名:SELECT ${columnName} FROM ...
  • 动态排序:ORDER BY ${sortField} ${sortOrder}

这些场景的共同点:SQL 标识符无法用 ? 占位符替代 ,因为 JDBC 的 ? 只能替换"值",不能替换表名/列名/关键字。


5. 安全写法:用白名单/<choose> 替代 ${}

5.1 推荐:<choose> 显式枚举可排序字段(避免 ${sortField})

xml 复制代码
<select id="findUsers" resultType="User">
  SELECT *
  FROM user
  <choose>
    <when test="sortField == 'age'">ORDER BY age</when>
    <when test="sortField == 'name'">ORDER BY name</when>
    <otherwise>ORDER BY id</otherwise>
  </choose>
  <choose>
    <when test="sortOrder == 'DESC'">DESC</when>
    <otherwise>ASC</otherwise>
  </choose>
</select>

特点:

  • 没有 ${},注入面大幅收敛
  • 允许的字段与方向被限制在固定集合

5.2 业务侧白名单映射(仍需谨慎)

在 Java 侧把外部输入映射成安全枚举,再传给 Mapper:

  • sortField 只能从 {"age","name","id"} 中选
  • sortOrder 只能是 ASC/DESC

即使最终仍用 ${},也要确保输入不可直接透传。


6. 常见坑(面试/实战都爱问)

6.1 ${} 会不会自动加引号?

不会。${} 就是纯拼接:

  • WHERE name = ${name} 若 name=Alice 会变成 WHERE name = Alice(SQL 语法可能错误)
  • 若你写成 WHERE name = '${name}' 才会带引号,但这会把问题变成"手写拼接字符串",更容易注入

6.2 LIKE 怎么写?

推荐写法(仍用 #{}):

xml 复制代码
WHERE name LIKE CONCAT('%', #{name}, '%')

不要写:

  • WHERE name LIKE '%${name}%'(高风险)

6.3 IN 查询怎么写?

  • 多值应使用 <foreach> 生成多个 #{} 占位符,而不是把整段字符串塞进 ${}。

7. 性能与缓存(知道即可)

  • #{}:SQL 形态更稳定(? 占位),数据库更容易复用执行计划;MyBatis 也更容易复用 MappedStatement 下的预处理流程。
  • ${}:SQL 文本随输入变化,可能导致数据库解析/硬解析次数增多(不同数据库表现不同)。

8. 结论(一句话复述)

  • 能用 #{} 就别用 ${}。
  • ${} 只能用于"表名/列名/排序"等无法参数绑定 的 SQL 片段,并且必须白名单,否则就是给 SQL 注入开门。
相关推荐
怕浪猫8 小时前
红利窗口开着,程序员别在场边囤课
面试·github·ai编程
凤凰院凶涛QAQ9 小时前
《Java版数据结构 & 集合类剖析》Map & Set & 反射:“从哈希碰撞到红黑树转换,反射帮你扒开 HashMap 的桶数组看个究竟”
java·数据结构·算法
H.莓飛9 小时前
【数据结构】排序_OJ题(一):计数、桶与排序后的处理
java·开发语言·数据结构
wangchunyu1149 小时前
JAVA AQS与Lock实现原理(含CAS)
java·开发语言·word
w_zero_one11 小时前
算法--模拟
java·数据结构·算法
Javatutouhouduan11 小时前
阿里1658页Java面试题总结(含答案)
java·高并发·java面试·java面试题·后端开发·java程序员·java八股文
秋天的怀念_David11 小时前
算法一千零一夜 · 第十三夜 幻方,武侠世界里的数学迷宫
java·程序人生·算法
lll235269177911 小时前
HR如何快速整理面试沟通内容?
面试·职场和发展
专业程序开发源11 小时前
springboot欧缇蔓月子中心管理系统82697-计算机课程设计、毕业设计
java·spring boot·后端·python·spring·django·课程设计
零售校长12 小时前
西湖区零售门店店长面试怎么准备
面试·职场和发展·零售