菜单

Administrator
发布于 2026-05-15 / 1 阅读
0
0

mybatis中的#和$的区别

MyBatis 中 #{} 和 ${} 的区别详解

一、概述

在 MyBatis 中,#{}${} 都用于在 SQL 语句中引用参数,但它们在底层实现、安全性和使用场景上存在本质区别。

对比维度 #{} ${}
处理方式 预编译参数(PreparedStatement) 直接字符串替换
SQL 注入 可防止 SQL 注入 无法防止 SQL 注入
参数类型 自动加引号(字符串类型加单引号) 直接拼接,不额外加引号
适用场景 普通参数传递(值) 动态表名、字段名、ORDER BY 等
性能 较高(可复用预编译 SQL) 较低(每次重新编译)

二、核心区别详解

1. #{} — 预编译参数

#{} 使用 PreparedStatement 进行预编译处理。MyBatis 会将 SQL 中的 #{} 部分替换为占位符 ?,然后通过 PreparedStatement.setXxx() 方法设置参数值。

示例:

-- Mapper 中的 SQL
SELECT * FROM user WHERE name = #{name};

-- 预编译后的 SQL
SELECT * FROM user WHERE name = ?;

-- 实际执行(假设 name = "zhangsan")
SELECT * FROM user WHERE name = 'zhangsan';

特点:

  • 参数被自动转义,传入的值会被当作一个字符串处理,自动加上单引号。
  • 可以有效防止 SQL 注入攻击。
  • 相同的 SQL 可以缓存预编译结果,提高性能。

2. ${} — 字符串直接替换

${}简单的字符串替换,MyBatis 在解析 SQL 阶段直接将 ${} 替换为参数值,不经过预编译处理。

示例:

-- Mapper 中的 SQL
SELECT * FROM ${tableName} WHERE name = #{name};

-- 解析后(假设 tableName = "user")
SELECT * FROM user WHERE name = ?;

特点:

  • 直接将参数值拼接到 SQL 语句中,不加任何转义
  • 存在 SQL 注入风险,不建议用户输入的内容使用 ${}
  • 适合动态传入数据库对象(表名、字段名、ORDER BY 排序字段等)。

三、SQL 注入问题分析

使用 #{} 安全的情况

-- 假设 name = "zhangsan' OR '1'='1"
SELECT * FROM user WHERE name = #{name};
-- 预编译后:SELECT * FROM user WHERE name = 'zhangsan'' OR ''1''=''1';
-- 安全,不会导致注入

使用 ${} 危险的情况

-- 假设 tableName = "user; DROP TABLE user; --"
SELECT * FROM ${tableName} WHERE name = #{name};
-- 解析后:SELECT * FROM user; DROP TABLE user; -- WHERE name = ?;
-- 危险!数据库表被删除

结论: 只要允许用户输入的内容作为参数,就必须使用 #{} 来防止 SQL 注入。只有在传入数据库对象名(表名、字段名等)时才考虑使用 ${},且必须严格校验输入内容。


四、什么场景必须用 ${}

虽然推荐优先使用 #{},但在以下场景中必须使用 ${}

  1. 动态表名

    SELECT * FROM ${tableName} WHERE id = #{id};
    
  2. 动态字段名

    SELECT ${columnName} FROM user WHERE id = #{id};
    
  3. ORDER BY 子句

    SELECT * FROM user ORDER BY ${orderField} ${orderDirection};
    

    注意:ORDER BY 后面不支持占位符 ?,只能用 ${}

  4. LIKE 模糊查询(某些写法)

    -- #{} 方式(推荐,安全)
    SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%');
    
    -- ${} 方式(不推荐,有注入风险)
    SELECT * FROM user WHERE name LIKE '%${name}%';
    

五、源码层面理解

MyBatis 在解析 Mapper XML 时:

  1. #{} → 创建 ParameterMapping 对象,生成 ? 占位符 → 执行时通过 PreparedStatement.setXxx() 设置参数 → 由 数据库驱动 完成转义。

  2. ${} → 在 DynamicSqlSource.getBoundSql() 阶段直接替换字符串 → 生成的 SQL 中已经包含实际值 → 后续解析为普通 SQL 执行。


六、总结

要点 说明
优先使用 #{} 安全、高效,是传递参数值的首选方式
谨慎使用 ${} 仅限于动态SQL中的表名、字段名、排序等场景
${} 必做校验 使用 ${} 时,必须对参数进行白名单校验或严格过滤
混合使用 可在同一 SQL 中混合使用:SELECT * FROM ${tableName} WHERE name = #{name}
性能差异 JDK 1.8+ 的 PreparedStatement 有预编译缓存,#{} 性能优于 ${}

最佳实践:能用 #{} 的绝不用 ${},不得不用 ${} 时一定要做好输入校验。


评论