在 MyBatis 中,$ 和 # 的区别很关键,关系到 SQL 注入风险和 SQL 的动态拼接方式。其中:什么时候 $ 不能换成 #,什么时候能换?
一、先简单回顾一下 $ 和 # 的区别:
-
#:安全的参数替换方式 ,使用预编译机制,会将参数作为占位符传给数据库,避免 SQL 注入。sqlSELECT * FROM user WHERE name = #{name}最终执行的是:
sqlSELECT * FROM user WHERE name = ?然后由 JDBC 设置参数。
-
$:直接文本替换 ,是在 SQL 语句生成阶段将参数原样拼接 进 SQL 中,容易引起 SQL 注入。sqlSELECT * FROM user ORDER BY ${column}如果
column = "name",最终 SQL 是:sqlSELECT * FROM user ORDER BY name
二、不能直接用 # 替换 $ 的情况(即必须用 $):
这些都是必须动态拼接 SQL 结构或关键字的情况:
-
动态字段名或表名
sqlSELECT ${column} FROM ${table}- 你不能写成
#{column},因为数据库不允许字段名或表名是占位符。 - 表名/字段名不能作为参数预编译处理,只能拼接进去。
- 你不能写成
-
动态排序
bashORDER BY ${sortField} ${sortOrder}- 如果用
#,会变成语法错误,SQL 无法执行。
- 如果用
-
动态 where 条件字段
bashWHERE ${field} = #{value}- 字段名不能预编译,因此必须用
${field}。
- 字段名不能预编译,因此必须用
三、可以用 # 替换 $ 的情况:
-
凡是值的部分(常规参数)都应该使用
#,而不是$:bashWHERE id = #{id} AND name = #{name} AND created_at >= #{startTime}
只要不是拼接字段名、表名、排序等 SQL 结构,基本都可以用 #。
四、小结
| 使用场景 | 推荐用法 | 原因说明 |
|---|---|---|
| 字段值 / 参数值 | #{} |
安全,防止 SQL 注入 |
| 字段名 / 表名 | ${} |
不能使用预编译,只能拼接 |
| 动态排序字段 / 顺序 | ${} |
语法需要拼接关键字 |
| 拼接完整 where 子句 | ${} |
需要直接拼接结构(如多个条件) |
五、安全建议
如果你必须使用
${},一定要白名单校验输入值,避免 SQL 注入风险。
比如:
arduino
if (!ALLOWED_COLUMNS.contains(sortField)) {
throw new IllegalArgumentException("Invalid column");
}