侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

MyBatis中#{}和${}的区别及防止SQL注入的最佳实践

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

题目

MyBatis中#{}和${}的区别及防止SQL注入的最佳实践

信息

  • 类型:问答
  • 难度:⭐⭐

考点

MyBatis参数占位符,SQL注入防护,动态SQL

快速回答

核心区别:

  • #{}:预编译处理,自动添加单引号防止SQL注入
  • ${}:字符串直接替换,存在SQL注入风险

最佳实践:

  1. 普通参数值必须使用#{}占位符
  2. 动态表名/列名等无法预编译的场景才使用${},并需严格校验参数
  3. 永远不要用${}处理用户输入值
## 解析

一、核心原理

#{}工作原理:

  • MyBatis会将#{}替换为JDBC的?占位符
  • 通过PreparedStatement预编译执行,参数值类型安全处理
  • 示例SQL:SELECT * FROM user WHERE name = #{name} → 实际执行:SELECT * FROM user WHERE name = ?

${}工作原理:

  • 直接字符串替换,不做任何转义处理
  • 相当于SQL字符串拼接
  • 示例SQL:SELECT * FROM ${tableName} → 若tableName='user; DROP TABLE user;' 将导致灾难性后果

二、代码示例对比

<!-- 安全示例 -->
<select id="safeQuery" resultType="User">
  SELECT * FROM users 
  WHERE username = #{username}  <!-- 预编译安全 -->
</select>

<!-- 危险示例 -->
<select id="dangerousQuery" resultType="User">
  SELECT * FROM users 
  WHERE username = '${username}'  <!-- 直接拼接危险! -->
</select>

当传入参数:username = "admin' OR '1'='1"

  • #{}版本:安全执行,查询用户名为admin' OR '1'='1的记录
  • ${}版本:产生SQL注入,实际执行:SELECT * FROM users WHERE username = 'admin' OR '1'='1' 返回所有数据

三、最佳实践

  1. 默认使用#{}原则:所有值类型参数(String/Integer/Date等)必须使用#{}占位符
  2. ${}使用场景限制
    • 动态表名:SELECT * FROM ${tableName}
    • 动态列名:ORDER BY ${columnName}
    • SQL函数/关键字:${dateFunc}(NOW())
  3. ${}参数校验规范
    // 在Mapper接口中添加校验
    List<User> selectByTable(
      @Param("tableName") String tableName) {
    
      // 白名单校验
      Set<String> validTables = Set.of("users", "products");
      if(!validTables.contains(tableName)) {
        throw new IllegalArgumentException("Invalid table name");
      }
      // ...
    }

四、常见错误

  • 错误1:在LIKE语句错误使用${}
    <!-- 错误写法 -->
    WHERE name LIKE '%${keyword}%'
    
    <!-- 正确写法 -->
    WHERE name LIKE CONCAT('%', #{keyword}, '%')
  • 错误2:IN语句错误使用${}
    <!-- 危险写法 -->
    WHERE id IN (${ids})
    
    <!-- 安全写法 -->
    WHERE id IN 
    <foreach item="id" collection="ids" open="(" separator="," close=")">
      #{id}
    </foreach>

五、扩展知识

  • 底层机制:MyBatis通过ParameterHandler处理#{},通过SqlSourceBuilder处理${}
  • 预编译优势
    • 数据库可缓存执行计划提升性能
    • 避免数据类型转换错误
    • 防止特殊字符(如单引号)导致语法错误
  • 特殊场景处理
    • 动态SQL:使用<if>, <choose>等标签组合#{}实现安全动态查询
    • 批量插入:使用<foreach>配合#{}实现安全批量操作