mybatis #和 的区别(超详细)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
在使用 MyBatis 进行数据库操作时,开发者经常需要在 SQL 语句中传递参数。此时,#
和 $
符号是 MyBatis 提供的两种参数占位符,它们看似相似,但底层实现和使用场景却截然不同。对于编程初学者而言,这两个符号的混淆可能导致 SQL 注入漏洞或查询逻辑错误;而中级开发者可能因对细节理解不深,无法在复杂场景中灵活选择。本文将通过循序渐进的方式,结合具体案例,深入解析 #
和 $
的区别,并提供实用建议。
一、MyBatis 参数占位符的基础概念
在 MyBatis 中,参数占位符用于将外部传入的值动态插入到 SQL 语句中。常见的占位符有两种:
#
符号:代表预编译处理(Prepared Statement)。$
符号:代表直接字符串拼接(String Concatenation)。
1.1 #
符号的预编译机制
#
符号的核心是预编译,即 MyBatis 会将参数值作为 JDBC 的 PreparedStatement
的参数传递。其底层原理类似于以下代码:
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "张三");
ResultSet rs = pstmt.executeQuery();
预编译机制通过将参数与 SQL 语句分开处理,有效防止 SQL 注入攻击。例如,当参数值为 ' OR '1'='1
时,#
符号会将其转义为字符串,而非作为 SQL 语句的一部分执行。
1.2 $
符号的直接拼接机制
$
符号则会直接将参数值替换到 SQL 字符串中。例如:
<select id="selectUser" resultType="User">
SELECT * FROM users WHERE name = '${name}'
</select>
当参数 name
的值为 张三
时,生成的 SQL 语句为:
SELECT * FROM users WHERE name = '张三'
但若参数值为恶意字符串 ' OR '1'='1
,则生成的 SQL 可能变为:
SELECT * FROM users WHERE name = ' OR '1'='1'
此时,攻击者可能绕过原本的查询逻辑,导致 SQL 注入漏洞。
二、#
和 $
的核心区别
2.1 数据类型处理方式
特性 | # 符号 | $ 符号 |
---|---|---|
参数处理 | 预编译(参数作为 JDBC 参数值) | 直接替换(参数作为字符串拼接) |
SQL 注入防护 | 自动防护 | 无防护(需手动处理) |
适用场景 | 动态值(如用户输入) | 静态值或安全可控的动态值 |
性能表现 | 略低(因预编译开销) | 略高(直接拼接无额外处理) |
2.2 具象化比喻:快递包裹 vs 直接邮寄
#
符号:如同将包裹密封后交给快递公司,包裹内容不会被中途篡改。$
符号:如同直接邮寄明信片,内容可能被他人读取或修改。
三、实际案例对比与代码示例
3.1 案例 1:基本查询场景
需求:根据用户名查询用户信息。
3.1.1 使用 #
符号的场景
<select id="selectUserByName" resultType="User">
SELECT * FROM users WHERE name = #{name}
</select>
特点:
- 参数
name
会被自动转义,例如' OR '1'='1
会被处理为'\' OR \'1\'=\'1
。 - 适用于用户输入等不可信数据。
3.1.2 使用 $
符号的场景
<select id="selectUserByName" resultType="User">
SELECT * FROM users WHERE name = '${name}'
</select>
风险:
- 若参数
name
来自用户输入,可能导致 SQL 注入。例如,输入张三'; DROP TABLE users; --
将直接执行恶意 SQL。
3.2 案例 2:动态拼接 SQL 片段
需求:根据动态条件拼接查询条件,例如动态生成 ORDER BY
子句。
3.2.1 使用 $
符号的场景
<select id="selectUsersWithOrder" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">
AND name = '${name}'
</if>
<if test="age != null">
AND age = ${age}
</if>
</where>
ORDER BY ${orderByField} ${orderDirection}
</select>
适用性:
- 当需要动态拼接 SQL 关键字(如字段名、排序方向)时,必须使用
$
符号。 - 注意:
orderByField
和orderDirection
必须来自可信源,否则存在 SQL 注入风险。
3.2.2 使用 #
符号的局限性
若尝试用 #
替代 $
,例如:
ORDER BY #{orderByField} #{orderDirection}
生成的 SQL 将包含单引号,导致语法错误:
ORDER BY 'name' 'ASC'
因此,#
无法处理动态字段名或关键字。
四、进阶场景与注意事项
4.1 参数类型的影响
当参数为集合或对象时,#
和 $
的行为会进一步分化:
-
集合类型(如 List):
#{list}
会将集合转为字符串(如[1,2,3]
)。${list}
则直接拼接字符串,需配合IN
子句使用:SELECT * FROM items WHERE id IN (${ids})
此时
ids
应为形如1,2,3
的字符串。
-
对象类型:
#{user.name}
可通过对象导航获取属性值,而$
符号无法直接支持此功能。
4.2 性能与缓存的考量
- 预编译缓存:
#
符号的 SQL 语句因预编译可被数据库缓存,重复执行时性能更优。 - 字符串拼接:
$
符号生成的 SQL 每次可能不同(如动态字段名),导致缓存利用率低。
五、最佳实践与总结
5.1 使用 #
的场景
- 用户输入参数:如搜索框内容、表单提交数据。
- 数值型参数:如
age = #{age}
。 - 需要 SQL 注入防护的场景。
5.2 使用 $
的场景
- 动态 SQL 关键字:如字段名、排序方向、表名。
- 可信的静态值:如固定配置参数(需严格控制来源)。
5.3 总结
#
和 $
的区别可概括为:
- 安全与防护:
#
是默认选择,尤其处理不可信数据时。 - 灵活性:
$
适用于需要直接控制 SQL 字符串的场景,但需严格确保输入安全。
通过合理选择占位符,开发者既能保证系统安全性,又能灵活应对复杂业务需求。
希望本文能帮助开发者深入理解 mybatis #和 $的区别
,在实际开发中做出更明智的选择。