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 注入开门。
相关推荐
uzong9 小时前
一个好的面试官,应该让候选人感到被尊重
面试
咩咩啃树皮10 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
鱟鲥鳚11 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
触底反弹12 小时前
深入理解大模型采样:Temperature、Top-K、Top-P 的原理与实战
人工智能·算法·面试
大模型码小白12 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司13 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
沉默王二14 小时前
腾讯一面,我霸气反问:“你说你们在做Agent项目,说说 SubAgent、Plan 模式、Skill 调用这些你们都是怎么做的?”面试官一直在擦汗。。
面试·agent·ai编程
爱笑的源码基地14 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
憧憬成为web高手14 小时前
皮卡丘靶场速通--sql 2
数据库·sql·mybatis
wuqingshun31415914 小时前
TCP超时重传机制是为了解决什么问题?
java