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 语句中。常见的占位符有两种:

  1. # 符号:代表预编译处理(Prepared Statement)。
  2. $ 符号:代表直接字符串拼接(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 关键字(如字段名、排序方向)时,必须使用 $ 符号。
  • 注意orderByFieldorderDirection 必须来自可信源,否则存在 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 #和 $的区别,在实际开发中做出更明智的选择。

最新发布