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 注入开门。
相关推荐
xcl09257 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐8 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长9 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn9 小时前
Spring 注解使用详解
java·spring
一条小小yu9 小时前
Spring IoC的理解
java·后端·spring
落魄实习生9 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈9 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
钝挫力PROGRAMER10 小时前
开发踩坑记:MyBatis selectKey、空 SQL
数据库·sql·mybatis
自强的小白11 小时前
核心功能(Service接口)
java·mybatis