在MyBatis中,#{}
和${}
是两种参数替换方式,核心区别在于安全性、预编译处理和使用场景。以下是详细对比:
1. 核心区别
特性 | **#{}** | **${}** |
---|---|---|
处理方式 | 预编译(PreparedStatement),参数替换为占位符? | 字符串直接替换(字符串拼接) |
安全性 | ✅ 防止SQL注入 | ❌ 存在SQL注入风险 |
适用场景 | 参数值替换(如WHERE条件、INSERT值) | 动态SQL片段(如表名、列名、排序字段) |
引号处理 | 自动根据类型添加引号(如字符串加'' ) | 直接替换,需手动处理引号(如'${value}' ) |
2. 使用示例
**(1) 查询用户(安全场景)**
xml
<!-- 使用#{},生成预编译SQL -->
<select id="getUser" resultType="User">
SELECT * FROM user WHERE name = #{name}
</select>
生成的SQL:
sql
SELECT * FROM user WHERE name = ? -- 参数值被预编译处理
**(2) 动态表名(必须用${})**
xml
<!-- 使用${}直接替换表名 -->
<select id="getLogs" resultType="Log">
SELECT * FROM ${tableName} WHERE type = #{type}
</select>
参数:tableName = "sys_log_2023", type = "error"
生成的SQL:
sql
SELECT * FROM sys_log_2023 WHERE type = 'error' -- 表名直接拼接
**(3) SQL注入风险示例**
xml
<!-- 恶意输入导致SQL注入 -->
<select id="getUser" resultType="User">
SELECT * FROM user WHERE name = '${name}'
</select>
参数:name = "admin' OR '1'='1"
生成的SQL:
sql
SELECT * FROM user WHERE name = 'admin' OR '1'='1' -- 返回所有用户数据
3. 最佳实践
-
优先使用
#{}
:
适用于所有参数值替换(如WHERE条件、INSERT/UPDATE的字段值)。xml
INSERT INTO user (name, age) VALUES (#{name}, #{age})
-
谨慎使用
${}
:
仅在动态SQL片段(如表名、列名、排序字段)时使用,并严格校验参数来源。xml
ORDER BY ${sortField} ${sortOrder} -- 确保sortField和sortOrder值合法
-
模糊查询的正确写法:
使用CONCAT
函数结合#{}
,而非直接拼接%
。xml
SELECT * FROM user WHERE name LIKE CONCAT('%', #{keyword}, '%')
4. 底层原理
机制 | **#{}** | **${}** |
---|---|---|
替换阶段 | 在SQL预编译阶段替换为? ,由JDBC驱动处理类型 | 在SQL解析阶段直接替换为字符串 |
数据库优化 | 预编译SQL可被缓存,提升执行效率 | 每次生成新SQL,需重新解析 |
特殊字符转义 | 自动转义(如单引号' → '' ) | 直接替换,需手动转义(如'${value}' ) |
5. 常见问题
**(1) 何时必须用${}?**
- 动态表名/列名:
xml
SELECT * FROM ${tableName} WHERE ${column} = #{value}
- SQL关键字(如排序方式):
xml
ORDER BY create_time ${order}
**(2) 如何防止${}的SQL注入?**
- 白名单校验:检查参数值是否在允许的范围内(如预定义的列名、表名)。
- 避免用户输入:动态SQL片段应由系统生成,而非直接接受用户输入。
总结
- **
#{}
**:安全、预编译、自动类型处理,适用于参数值替换。 - **
${}
**:灵活但危险,直接字符串替换,适用于动态SQL片段。 - 核心原则:能用
#{}
则不用${}
,必须用${}
时需严格校验参数合法性。