文章目录
- 一、概述
- 二、`#{}` 和 `${}` 的核心区别
- 1. 底层机制
- 代码示例
- 2. 核心区别总结
- 三、为什么表名只能用 `${}`?
- 1. 预编译机制的限制
- 2. 动态表名的实现
- 四、安全性注意事项
- 1. `${}` 的风险场景
- 2. 安全实践
- 五、面试高频考点
- 1. 基础原理类问题
- **问题 1**:
- **问题 2**:
- 2. 安全与设计类问题
- **问题 3**:
- **问题 4**:
- 3. 扩展实战类问题
- **问题 5**:
- 六、总结与最佳实践
- 1. 使用场景对比
- 2. 最佳实践
- 七、附录:MyBatis 预编译原理图示
一、概述
在 MySQL 和 MyBatis 等框架中,${}
和 #{}
是动态 SQL 中常用的占位符。它们的核心差异在于 预编译机制 和 安全性,正确使用二者是后端开发的基本功,也是面试中的高频考点。本文将从原理、场景、安全性及面试题四方面深入解析。
二、#{}
和 ${}
的核心区别
1. 底层机制
占位符 | 预编译 | 替换方式 | 安全性 |
---|---|---|---|
#{} | ✅ | 参数化绑定 (? ) | 高 |
${} | ❌ | 字符串直接拼接 | 低 |
代码示例
-- #{}
SELECT * FROM users WHERE name = #{name};
-- 预编译后:SELECT * FROM users WHERE name = ?;
-- ${}
SELECT * FROM ${table} WHERE id = 1;
-- 替换后:SELECT * FROM users_2023 WHERE id = 1;
2. 核心区别总结
#{}
:
用于替换 值类型(如 WHERE 条件值、INSERT 字段值),通过PreparedStatement
预编译,防止 SQL 注入。${}
:
用于替换 标识符(如表名、列名、ORDER BY 子句),直接拼接字符串,需手动校验安全性。
三、为什么表名只能用 ${}
?
1. 预编译机制的限制
- 数据库协议限制:预编译占位符
?
仅支持替换值类型(字符串、数字等),不能替换表名、列名等标识符。 - 语法合法性:以下写法直接报错:
-- 错误!表名无法预编译 SELECT * FROM ? WHERE id = 1;
2. 动态表名的实现
若需根据业务逻辑动态切换表(如分表场景),只能通过字符串拼接:
<!-- MyBatis 示例 -->
<select id="selectLogs" resultType="Log">
SELECT * FROM logs_${month}
</select>
四、安全性注意事项
1. ${}
的风险场景
// 恶意输入导致 SQL 注入
String userInput = "users; DROP TABLE users; --";
String sql = "SELECT * FROM " + userInput;
// 执行结果:SELECT * FROM users; DROP TABLE users; --
2. 安全实践
- 禁止用户控制表名:表名应在代码层生成(如根据时间分表),而非直接传递用户输入。
- 白名单校验:若必须动态传参,需校验参数格式(如正则匹配
^[a-zA-Z0-9_]+$
)。 - SQL 审计:拦截非常规表名操作(如
information_schema
)。
五、面试高频考点
1. 基础原理类问题
问题 1:
“MyBatis 中 #{}
和 ${}
的底层实现有什么区别?”
答:
#{}
使用PreparedStatement
预编译,参数替换为?
,防止 SQL 注入。${}
直接拼接字符串,无预编译,需手动处理安全性。
场景:
面试官考察候选人对 MyBatis 执行过程的理解,是否清楚预编译机制。
问题 2:
“为什么表名必须用 ${}
?能否用 #{}
?”
答:
数据库协议规定预编译占位符 ?
只能替换值类型,不能替换表名、列名等标识符。若强行使用 #{}
,最终生成的 SQL 会因语法错误无法执行。
场景:
面试中常见于考察 SQL 预编译机制的底层知识。
2. 安全与设计类问题
问题 3:
“如何安全地使用 ${}
动态指定表名?”
答:
- 代码层控制:表名由系统生成(如
user_2023
),而非用户传入。 - 白名单校验:若需外部传入,校验参数是否符合命名规范(如正则匹配)。
- 日志监控:记录所有动态表名操作,便于审计。
场景:
考察安全意识和实际工程经验,常见于金融、数据安全相关岗位。
问题 4:
“除了表名,还有哪些场景必须用 ${}
?”
答:
- 动态列名:
SELECT ${column} FROM table
- 排序字段:
ORDER BY ${sortField}
- 动态 SQL 片段:
<if test="condition">${sqlSegment}</if>
场景:
面试官可能延伸考察动态 SQL 的灵活应用能力。
3. 扩展实战类问题
问题 5:
“如果必须让用户传入表名,如何设计安全方案?”
答:
- 前端传递表名编码(如
1=users, 2=products
),后端映射为真实表名。 - 参数加密:用户传入加密参数,后端解密后匹配预定义表名。
- 数据库权限隔离:动态表操作使用只读低权限账号。
场景:
高级岗位考察系统设计能力,尤其是安全与灵活性平衡的方案。
六、总结与最佳实践
1. 使用场景对比
场景 | 占位符 | 示例 |
---|---|---|
WHERE 条件值 | #{} | WHERE id = #{id} |
动态表名/列名 | ${} | SELECT * FROM ${table} |
排序字段 | ${} | ORDER BY ${sort} |
2. 最佳实践
- 默认使用
#{}
:除非必须使用${}
。 - 最小化
${}
暴露:禁止用户传入未经验证的参数。 - 日志 + 监控:记录所有动态 SQL 操作,及时预警异常行为。
七、附录:MyBatis 预编译原理图示
MyBatis 执行流程:
1. 解析 XML SQL → 2. 替换 `#{}` 为 `?` → 3. 预编译 SQL → 4. 绑定参数值 → 5. 执行
文档说明:本文适用于初中级后端开发者巩固知识点,以及资深开发者面试复习。建议结合 MyBatis 源码和 MySQL 协议文档深入理解。