题目
MyBatis中#{}和${}的区别是什么?请说明各自的使用场景
信息
- 类型:问答
- 难度:⭐
考点
参数占位符,SQL注入,动态SQL
快速回答
在MyBatis中,#{}和${}都是参数替换符号,但存在关键区别:
- #{}:预编译处理,能防止SQL注入,适用于大多数参数传递场景
- ${}:字符串直接替换,有SQL注入风险,仅适用于动态表名/列名等特殊场景
最佳实践:优先使用#{},仅在必要时谨慎使用${}。
解析
1. 核心区别
#{}(预编译占位符):
- 工作原理:MyBatis会将其转换为JDBC的PreparedStatement参数占位符
? - 处理方式:自动添加单引号,对特殊字符进行转义
- 安全性:有效防止SQL注入
- 示例:
SELECT * FROM users WHERE name = #{userName}→ 编译为SELECT * FROM users WHERE name = ?
${}(字符串替换):
- 工作原理:直接替换为参数值的字符串
- 处理方式:不做任何转义处理,原样嵌入SQL
- 风险:可能引发SQL注入漏洞
- 示例:
SELECT * FROM ${tableName}→ 若tableName='users; DROP TABLE users;' 将导致灾难性后果
2. 代码示例对比
<!-- 安全示例:使用#{} -->
<select id="findUser" resultType="User">
SELECT * FROM users WHERE id = #{userId}
</select>
<!-- 风险示例:使用${} -->
<select id="dynamicTable" resultType="map">
SELECT * FROM ${tableName}
</select>3. 使用场景
- 必须使用#{}的场景:
- 所有WHERE条件中的值参数(如ID、姓名、日期等)
- INSERT/UPDATE语句中的字段值
- 超过99%的常规SQL参数传递
- 可考虑使用${}的场景:
- 动态表名/列名(需严格校验参数值)
- SQL关键字动态替换(如ORDER BY ${sortColumn})
- 注意:必须手动过滤危险字符(如分号、注释符等)
4. 最佳实践
- 默认始终优先使用
#{} - 使用
${}时:- 限制参数值为白名单值(如只允许[a-z_]+)
- 避免拼接用户输入的直接值
- 示例安全用法:
ORDER BY ${sortField} DESC(先校验sortField是否在允许字段列表中)
- XML中参数校验示例:
// 在Mapper接口中添加校验 List<User> getByTable(@Param("tableName") String tableName); // 在Service层校验 public List<User> safeQuery(String tableName) { if (!tableName.matches("[a-zA-Z0-9_]+")) { throw new IllegalArgumentException("Invalid table name"); } return userMapper.getByTable(tableName); }
5. 常见错误
- 混淆使用:在WHERE条件中使用
${}导致SQL注入风险 - 过度使用:在不需要动态SQL的地方使用
${} - 缺少校验:使用
${}时未对输入值做安全过滤 - 错误示例:
WHERE name = '${userInput}'(可直接注入' OR 1=1 --)
6. 扩展知识
- 预编译原理:数据库先编译SQL结构,再传递参数值,使注入攻击无法改变SQL结构
- OGNL表达式:
#{}内部可使用OGNL表达式如#{user.address.city} - 替代方案:动态表名场景可考虑数据库视图或设计优化避免拼接
- MyBatis-Plus:提供
@SqlParser等注解增强安全性