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

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

一句话总结

  • #{}预编译参数占位符?),走 PreparedStatementTypeHandler防 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=agesortOrder=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 注入开门。
相关推荐
2301_804947582 小时前
nginx
java·服务器·nginx
柒.梧.2 小时前
零基础吃透Java核心基础:JDK/JRE/JVM全解析+跨平台原理
java·开发语言·jvm
sheji34162 小时前
【开题答辩全过程】以 基于Java的宠物酒店管理系统设计与实现为例,包含答辩的问题和答案
java·开发语言·宠物
ServBay2 小时前
彻底重绘Spring Boot性能版图,资源占用缩减80%
java·spring boot·后端
威风的虫2 小时前
LangGraph的介绍
java·开发语言
康小庄2 小时前
Java阻塞队列——用法及常用场景
java·开发语言·数据库·spring boot·spring·jetty
yy.y--2 小时前
Java多线程实例:输出线程名20次
java·开发语言
SakitamaX3 小时前
Tomcat介绍与实验
java·tomcat