在 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");
}