侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

MyBatis中#{}和${}的区别是什么?请说明各自的使用场景

2025-12-13 / 0 评论 / 4 阅读

题目

MyBatis中#{}和${}的区别是什么?请说明各自的使用场景

信息

  • 类型:问答
  • 难度:⭐

考点

参数占位符,SQL注入,动态SQL

快速回答

在MyBatis中,#{}和${}都是参数替换符号,但存在关键区别:

  • #{}:预编译处理,能防止SQL注入,适用于大多数参数传递场景
  • ${}:字符串直接替换,有SQL注入风险,仅适用于动态表名/列名等特殊场景

最佳实践:优先使用#{},仅在必要时谨慎使用${}。

解析

1. 核心区别

#{}(预编译占位符)

  • 工作原理:MyBatis会将其转换为JDBC的PreparedStatement参数占位符?
  • 处理方式:自动添加单引号,对特殊字符进行转义
  • 安全性:有效防止SQL注入
  • 示例:SELECT * FROM users WHERE name = #{userName} → 编译为SELECT * FROM users WHERE name = ?

${}(字符串替换)

  • 工作原理:直接替换为参数值的字符串
  • 处理方式:不做任何转义处理,原样嵌入SQL
  • 风险:可能引发SQL注入漏洞
  • 示例:SELECT * FROM ${tableName} → 若tableName='users; DROP TABLE users;' 将导致灾难性后果

2. 代码示例对比

<!-- 安全示例:使用#{} -->
<select id="findUser" resultType="User">
  SELECT * FROM users WHERE id = #{userId}
</select>

<!-- 风险示例:使用${} -->
<select id="dynamicTable" resultType="map">
  SELECT * FROM ${tableName}  
</select>

3. 使用场景

  • 必须使用#{}的场景
    • 所有WHERE条件中的值参数(如ID、姓名、日期等)
    • INSERT/UPDATE语句中的字段值
    • 超过99%的常规SQL参数传递
  • 可考虑使用${}的场景
    • 动态表名/列名(需严格校验参数值)
    • SQL关键字动态替换(如ORDER BY ${sortColumn})
    • 注意:必须手动过滤危险字符(如分号、注释符等)

4. 最佳实践

  • 默认始终优先使用#{}
  • 使用${}时:
    • 限制参数值为白名单值(如只允许[a-z_]+)
    • 避免拼接用户输入的直接值
    • 示例安全用法:ORDER BY ${sortField} DESC(先校验sortField是否在允许字段列表中)
  • XML中参数校验示例:
    // 在Mapper接口中添加校验
    List<User> getByTable(@Param("tableName") String tableName);
    
    // 在Service层校验
    public List<User> safeQuery(String tableName) {
      if (!tableName.matches("[a-zA-Z0-9_]+")) {
        throw new IllegalArgumentException("Invalid table name");
      }
      return userMapper.getByTable(tableName);
    }

5. 常见错误

  • 混淆使用:在WHERE条件中使用${}导致SQL注入风险
  • 过度使用:在不需要动态SQL的地方使用${}
  • 缺少校验:使用${}时未对输入值做安全过滤
  • 错误示例:WHERE name = '${userInput}'(可直接注入' OR 1=1 --

6. 扩展知识

  • 预编译原理:数据库先编译SQL结构,再传递参数值,使注入攻击无法改变SQL结构
  • OGNL表达式#{}内部可使用OGNL表达式如#{user.address.city}
  • 替代方案:动态表名场景可考虑数据库视图或设计优化避免拼接
  • MyBatis-Plus:提供@SqlParser等注解增强安全性