MyBatis中 #{} 与 ${} 的区别
一、根本区别:预编译 vs 字符串拼接
这是两者最本质的区别,决定了它们在安全性、性能和行为上的所有不同。
| 特性 | #{}(井号占位符) | ${}(美元占位符) |
|---|---|---|
| 处理机制 | 预编译(PreparedStatement) | 字符串拼接/直接替换(Statement) |
| SQL构建方式 | SELECT * FROM user WHERE id = ? (先生成带?的SQL模板,再传值) | SELECT * FROM user WHERE id = 1 (直接将值拼入SQL字符串) |
| 参数处理 | 将传入的参数安全地设置到预编译语句中,会进行类型处理(如字符串自动加引号)。 | 将传入的参数原样替换到SQL字符串中,不做任何处理。 |
| 类比 | 就像填空题,题目(SQL结构)固定,你只需在空白处(?)填上安全的值。 | 就像作文题,你需要自己组织完整的语句(SQL),容易写错或引入危险内容。 |
二、核心差异详解
1. 安全性(最关键的区别)
-
#{}:能有效防止SQL注入。-
因为使用了JDBC的
PreparedStatement,参数值在预编译后传入,数据库会将其视为数据而非SQL指令的一部分。即使用户传入1 OR 1=1,也会被当作一个完整的字符串值,而不是可执行的SQL。 -
示例:
-- 传入参数:id = "1 OR 1=1" SELECT * FROM user WHERE id = #{id} -- 实际执行(安全): SELECT * FROM user WHERE id = '1 OR 1=1'
-
-
${}:无法防止SQL注入。-
因为它直接进行字符串替换,如果用户传入恶意参数,该参数会成为SQL语句的一部分并被数据库执行。
-
示例:
-- 传入参数:id = "1 OR 1=1" SELECT * FROM user WHERE id = ${id} -- 实际执行(危险!): SELECT * FROM user WHERE id = 1 OR 1=1 -- 这将返回所有用户数据!
-
2. 性能
-
#{}:通常性能更优。-
预编译语句(
PreparedStatement)可以被数据库缓存和重用。对于同构SQL(结构相同,参数不同),数据库服务器只需编译一次,后续执行效率高。
-
-
${}:性能相对较低。-
每次都会生成一条全新的SQL语句,数据库需要重新解析和编译。
-
3. 参数值的处理
-
#{}:自动处理类型和引号。-
如果传入的是字符串,MyBatis会自动加上单引号(
');如果传入数字,则不加。你无需手动处理引号。
<!-- 传入 name = '张三' --> SELECT * FROM user WHERE name = #{name} <!-- 转换为:SELECT * FROM user WHERE name = '张三' --> -
-
${}:原样替换,需手动处理引号。-
替换是“愚蠢”的字符串替换。如果值是字符串,你必须在参数值中或SQL片段中自行添加引号,否则会导致语法错误。
<!-- 错误:如果name是字符串,替换后SQL语法错误 --> SELECT * FROM user WHERE name = ${name} <!-- 正确:需要在值中或外围加上引号 --> SELECT * FROM user WHERE name = '${name}' <!-- 或者传递的参数值本身包含引号: `"'张三'"` --> -
4. 适用场景
-
#{}:适用于WHERE条件、SET子句、VALUES子句等几乎所有传递参数值的场景。应作为默认首选。 -
${}:适用于动态传入 SQL片段**,如:-
表名:
SELECT * FROM ${tableName} -
列名:
ORDER BY ${columnName} -
动态排序字段:
ORDER BY ${orderByColumn} ${orderByDirection} -
数据库函数/特殊SQL关键字:
SELECT ${columns} FROM ... -
注意:使用
${}时,必须确保参数值绝对安全(如来自内部枚举、白名单校验),绝不能直接接收用户输入,否则风险极高。
-
945

被折叠的 条评论
为什么被折叠?



