侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

MyBatis中#{}和${}的区别及SQL注入防范

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

题目

MyBatis中#{}和${}的区别及SQL注入防范

信息

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

考点

参数占位符原理,SQL注入防范,动态SQL使用场景

快速回答

核心区别:

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

使用建议:

  1. 优先使用#{}处理用户输入
  2. ${}仅用于动态表名/列名等非值场景
  3. 使用${}时必须手动过滤参数
## 解析

一、核心原理对比

#{} 工作原理(预编译):

<!-- Mapper示例 -->
<select id="findUser" resultType="User">
  SELECT * FROM users WHERE name = #{name}
</select>

执行过程:

  1. MyBatis解析SQL时会将#{name}替换为?
  2. 通过JDBC PreparedStatement设置参数值
  3. 最终执行:SELECT * FROM users WHERE name = ?

${} 工作原理(字符串替换):

<!-- Mapper示例 -->
<select id="findUser" resultType="User">
  SELECT * FROM ${tableName} WHERE name = '${name}'
</select>

执行过程:

  1. MyBatis直接拼接SQL:SELECT * FROM users WHERE name = 'Alice'
  2. 生成完整SQL语句后提交给数据库

二、关键差异总结

特性#{}${}
处理方式预编译占位符字符串替换
SQL注入风险安全高危
参数类型处理自动类型转换需手动处理
引号添加自动添加需手动添加
性能预编译可复用执行计划每次重新编译SQL

三、最佳实践与错误示例

正确用法:

  • #{} 用于值传递
    WHERE id = #{userId}
  • ${} 用于动态标识符
    ORDER BY ${columnName}

危险示例(SQL注入):

// 恶意参数传入
Map<String, Object> params = new HashMap<>();
params.put("orderBy", "name; DROP TABLE users;--");

// Mapper错误写法
<select id="getUsers" resultType="User">
  SELECT * FROM users ORDER BY ${orderBy}
</select>

执行结果:
SELECT * FROM users ORDER BY name; DROP TABLE users;--

四、安全使用${}的方案

方案1:白名单校验

// Service层校验
public List<User> getUsers(String orderBy) {
  if(!Arrays.asList("id", "name", "email").contains(orderBy)) {
    throw new IllegalArgumentException("Invalid column name");
  }
  return userMapper.getUsers(orderBy);
}

方案2:SqlInjectionFilter工具类

public class SafeSqlUtil {
  public static String filterColumn(String input) {
    if (!input.matches("[a-zA-Z0-9_]+")) {
      throw new IllegalArgumentException("Invalid characters");
    }
    return input;
  }
}

// Mapper中使用
<select id="getUsers" resultType="User">
  SELECT * FROM users ORDER BY ${@com.util.SafeSqlUtil@filterColumn(orderBy)}
</select>

五、特殊场景处理

动态表名场景:

<select id="getLogs" resultType="Log">
  SELECT * FROM logs_${yearMonth}
</select>

LIKE查询正确写法:

<!-- 错误:直接使用${} -->
WHERE name LIKE '%${name}%'

<!-- 正确:使用#{}配合CONCAT -->
WHERE name LIKE CONCAT('%', #{name}, '%')

<!-- 正确:使用bind标签 -->
<bind name="pattern" value="'%' + name + '%'" />
WHERE name LIKE #{pattern}

六、扩展知识

  • 预编译原理:数据库先编译SQL结构,再传入参数值,使注入数据无法改变SQL语义
  • MyBatis参数处理:通过ParameterHandler处理#{},通过SqlSourceBuilder处理${}
  • OGNL表达式:${}内部可使用OGNL表达式,如${@java.lang.Runtime@getRuntime().exec('calc')}(需防范表达式注入)

七、常见面试陷阱

问题: "为什么有时用${}排序比#{}快?"
解析: 当排序字段有索引时:
- #{}会产生ORDER BY 'name'(常量排序)
- ${}会产生ORDER BY name(字段排序)
解决方案: 必须用${}但需严格校验参数