侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

MyBatis 中如何安全高效地处理动态 IN 查询并防止 SQL 注入?

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

题目

MyBatis 中如何安全高效地处理动态 IN 查询并防止 SQL 注入?

信息

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

考点

动态SQL编写,SQL注入防护,批量查询性能优化

快速回答

在 MyBatis 中处理动态 IN 查询需要解决三个核心问题:

  1. 动态参数构建:使用 <foreach> 标签遍历集合
  2. SQL 注入防护:必须使用 #{} 预编译占位符
  3. 性能优化:避免超过数据库 IN 条件限制(如 Oracle 的 1000 条限制)

推荐解决方案:

  • 使用 <foreach> + #{} 构建安全查询
  • 通过分批查询解决大集合问题
  • 使用 @Param 注解明确集合名称
## 解析

1. 问题背景与挑战

在 MyBatis 中执行类似 SELECT * FROM users WHERE id IN (?) 的查询时,直接传入集合会导致:

  • SQL 注入风险:使用 ${} 拼接参数时恶意输入可能破坏查询
  • 性能问题:数据库对 IN 子句的参数数量有限制(如 Oracle 限制 1000 条)
  • 语法错误:集合直接映射为 IN (1,2,3) 但预编译占位符要求 IN (?)

2. 安全解决方案(核心代码)

<select id="selectUsersByIds" resultType="User">
  SELECT * FROM users
  WHERE id IN
  <foreach item="id" collection="ids" open="(" separator="," close=")">
    #{id}  <!-- 关键:必须用 #{} 防止注入 -->
  </foreach>
</select>

对应 Mapper 接口:

List<User> selectUsersByIds(@Param("ids") List<Long> ids);

3. 性能优化策略

3.1 分批查询(解决大集合问题)

// 服务层实现分批
public List<User> batchSelectUsers(List<Long> ids) {
  List<User> result = new ArrayList<>();
  // 每批 500 个 ID
  List<List<Long>> batches = Lists.partition(ids, 500); 

  for (List<Long> batch : batches) {
    result.addAll(userMapper.selectUsersByIds(batch));
  }
  return result;
}

3.2 临时表方案(超大数据集)

<!-- 1. 创建临时表 -->
<insert id="createTempTable">
  CREATE TEMP TABLE temp_ids (id BIGINT)
</insert>

<!-- 2. 批量插入 -->
<insert id="batchInsertIds">
  INSERT INTO temp_ids VALUES
  <foreach item="id" collection="ids" separator=",">
    (#{id})
  </foreach>
</insert>

<!-- 3. JOIN 查询 -->
<select id="selectByTempTable" resultType="User">
  SELECT u.* FROM users u
  JOIN temp_ids t ON u.id = t.id
</select>

4. 常见错误与防护

错误做法风险正确方案
WHERE id IN (${ids}) 高危 SQL 注入 使用 #{} + <foreach>
单次传入超 1000 个 ID 数据库报错(ORA-01795) 分批查询或临时表
忘记 @Param("ids") 抛出 BindingException 明确指定集合名称

5. 扩展知识

  • MyBatis 底层原理#{} 通过 PreparedStatement 实现预编译,${} 直接拼接 SQL
  • 集合类型选择ListArray 都支持,但 Map 需特殊处理
  • 动态 SQL 进阶:结合 <choose><if> 实现多条件分支
  • 极限优化:对于千万级数据,考虑异步导出或搜索引擎方案

6. 最佳实践总结

  1. 始终优先使用 #{} 避免注入
  2. 超过 500 个参数时分批查询
  3. 使用 @Param 明确集合变量名
  4. 查询字段加索引(特别是 JOIN 字段)
  5. 大数据量场景优先用临时表方案