目录
[① 导读卡片](#① 导读卡片)
[② 背景与目标](#② 背景与目标)
[③ 概念与原理](#③ 概念与原理)
[Collection 是什么?](#Collection 是什么?)
[selectBatchIds 方法签名](#selectBatchIds 方法签名)
[为什么不用 Integer\[\] 或 List 本身?](#为什么不用 Integer[] 或 List 本身?)
[④ 逻辑与对比](#④ 逻辑与对比)
[List vs Set 传参怎么选?](#List vs Set 传参怎么选?)
[⑤ 核心详解](#⑤ 核心详解)
[5.1 最推荐的传值方式:List](#5.1 最推荐的传值方式:List)
[5.2 场景一:前端传 JSON 数组(POST)](#5.2 场景一:前端传 JSON 数组(POST))
[5.3 场景二:前端传逗号分隔字符串(GET)](#5.3 场景二:前端传逗号分隔字符串(GET))
[5.4 场景三:前端传多个同名参数](#5.4 场景三:前端传多个同名参数)
[5.5 完整 Mapper 和 Service 示例](#5.5 完整 Mapper 和 Service 示例)
[5.6 ⚠️ 空值校验是必须的](#5.6 ⚠️ 空值校验是必须的)
[5.7 ⚡ 性能建议:ID 数量控制在 1000 以内](#5.7 ⚡ 性能建议:ID 数量控制在 1000 以内)
[⑥ 案例实战](#⑥ 案例实战)
[⑦ 避坑 & 最佳实践](#⑦ 避坑 & 最佳实践)
[❌ 常见错误](#❌ 常见错误)
[错误 1:想传 Collection 但不知道传什么](#错误 1:想传 Collection 但不知道传什么)
[错误 2:没做空值校验就调用](#错误 2:没做空值校验就调用)
[错误 3:字符串分割时没处理空格](#错误 3:字符串分割时没处理空格)
[✅ 最佳实践清单](#✅ 最佳实践清单)
[⑧ 总结 & 路线图](#⑧ 总结 & 路线图)
① 导读卡片
| 项目 | 内容 |
|---|---|
| 一句话定位 | 彻底搞懂 MyBatis-Plus 的 selectBatchIds(Collection<? extends Serializable>) 参数该传什么、怎么从前端获取、如何转换 |
| 适合人群 | Java 后端开发者、MyBatis-Plus 使用者、SpringBoot 项目新手 |
| 难度 | ⭐⭐(简单,适合所有 CRUD 开发者) |
| 阅读时长 | 8 分钟 |
| 前置知识 | MyBatis-Plus 基础 CRUD、SpringBoot 基本使用 |
② 背景与目标
为什么学?
很多同学刚用 MyBatis-Plus 时,看到 selectBatchIds(Collection<? extends Serializable> idList) 这个方法的参数类型会犯愁:
-
Collection 是接口,不能 new,到底传什么?
-
前端传的 ID 列表是字符串,怎么转?
-
传 ArrayList 还是 HashSet?
-
idList 为空会怎样?
这些问题看着简单,但实际写代码时就是卡住。
学完能怎样?
-
✅ 看到
Collection参数就知道传List -
✅ 能处理前端两种传参方式:数组 + 逗号分隔字符串
-
✅ 知道为什么要做空值校验
-
✅ 不会写出
WHERE id IN ()的 SQL 语法错误
③ 概念与原理
Collection 是什么?
Collection 是 Java 集合框架的 顶级接口,定义了一组元素的集合操作规范:
java
// Collection 接口的继承关系
Iterable
└── Collection ← 这是顶级接口
├── List ← 有序、可重复(ArrayList、LinkedList)
└── Set ← 无序、不可重复(HashSet、TreeSet)
selectBatchIds 方法签名
java
// 来自 MyBatis-Plus BaseMapper
List<T> selectBatchIds(Collection<? extends Serializable> idList)
-
参数 :
Collection<? extends Serializable>--- 任何实现了Serializable的元素的集合 -
返回值 :
List<T>--- 查询到的实体列表 -
底层 SQL :
WHERE id IN (?, ?, ?)
为什么不用 Integer\[\] 或 List 本身?
MyBatis-Plus 选择 Collection<? extends Serializable> 是为了 最大灵活性:
-
List<Integer>能用 ✅(List 实现 Collection,Integer 实现 Serializable) -
Set<String>能用 ✅(Set 实现 Collection,String 实现 Serializable) -
ArrayList<Long>能用 ✅ -
Collection<Integer>本身也能传 ✅(实际传的是它的实现类)
简单记:只要元素类型实现了 Serializable,集合是 List 或 Set 都可以传。
④ 逻辑与对比
List vs Set 传参怎么选?
| 对比维度 | List(推荐 ✅) | Set(特定场景) |
|---|---|---|
| 前端传参适配 | 天然适配数组/JSON 数组 | 需要手动转换 |
| ID 顺序 | 保留前端传入顺序 | 不保证顺序 |
| 重复 ID | 允许重复(SQL 中 IN 允许重复) | 自动去重 |
| 95% 场景使用 | 是 | 极少 |
前端传参方式对比
| 传参方式 | 示例 | 推荐度 |
|---|---|---|
| JSON 数组(POST) | {"ids": [1,2,3]} |
⭐⭐⭐⭐⭐ 最常用 |
| 逗号分隔字符串 | ?ids=1,2,3 |
⭐⭐⭐ 简单查询 |
| 多个同名参数 | ?ids=1&ids=2&ids=3 |
⭐⭐ 方便但少见 |
⑤ 核心详解
5.1 最推荐的传值方式:List
java
// ✅ 正确且推荐:传 List
List<Long> idList = Arrays.asList(1L, 2L, 3L);
userMapper.selectBatchIds(idList);
5.2 场景一:前端传 JSON 数组(POST)
前端请求:
POST /user/batch/query Content-Type: application/json {"ids": 1, 2, 3}
后端代码:
java
@Data
public class BatchQueryDTO {
private List<Long> ids; // 直接映射前端 JSON 数组
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@PostMapping("/batch/query")
public List<User> batchQuery(@RequestBody BatchQueryDTO dto) {
List<Long> idList = dto.getIds();
// ⚠️ 重要:空值校验
if (idList == null || idList.isEmpty()) {
return Collections.emptyList();
}
return userMapper.selectBatchIds(idList);
}
}
5.3 场景二:前端传逗号分隔字符串(GET)
前端请求:
GET /user/batch/query?ids=1,2,3
后端代码:
java
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/batch/query")
public List<User> batchQuery(@RequestParam("ids") String idsStr) {
// 1. 空值校验
if (StringUtils.isEmpty(idsStr)) {
return Collections.emptyList();
}
// 2. 分割字符串 → 转 Long → 收集成 List
List<Long> idList = Arrays.stream(idsStr.split(","))
.map(String::trim) // 去除空格
.map(Long::parseLong) // 转 Long
.collect(Collectors.toList());
// 3. 调 MyBatis-Plus
return userMapper.selectBatchIds(idList);
}
}
5.4 场景三:前端传多个同名参数
前端请求:
GET /user/batch/query?ids=1&ids=2&ids=3
后端代码:
java
@GetMapping("/batch/query")
public List<User> batchQuery(@RequestParam("ids") List<Long> ids) {
// Spring 会自动将多个同名参数解析为 List
// 注意:这里 ids 可能是 null 或空列表
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
return userMapper.selectBatchIds(ids);
}
5.5 完整 Mapper 和 Service 示例
java
// ====== 实体类 ======
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
}
// ====== Mapper 接口 ======
@Repository
public interface UserMapper extends BaseMapper<User> {
// 继承 BaseMapper 就有了 selectBatchIds 方法
}
// ====== Service ======
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> getUsersByIds(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
return userMapper.selectBatchIds(ids);
}
}
5.6 ⚠️ 空值校验是必须的
java
// ❌ 错误示范:空列表传进去会报 SQL 语法错误
List<Long> ids = new ArrayList<>(); // 空列表
userMapper.selectBatchIds(ids);
// → SQL: WHERE id IN () → ❌ SQL 语法错误
// ✅ 正确做法:判断空值
if (ids == null || ids.isEmpty()) {
return Collections.emptyList(); // 返回空结果
}
5.7 ⚡ 性能建议:ID 数量控制在 1000 以内
java
// 当 ID 数量过大时,分批查询
public List<User> getUsersByIdsSafe(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
List<List<Long>> batches = Lists.partition(ids, 1000); // Guava 工具
List<User> result = new ArrayList<>();
for (List<Long> batch : batches) {
result.addAll(userMapper.selectBatchIds(batch));
}
return result;
}
⑥ 案例实战
完整案例:批量查询用户信息
需求: 前端传选中的用户 ID,后端返回用户列表。
java
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 场景:前端表格多选后点击"批量查询"
* 请求:POST /api/user/batch
* Body: {"ids": [1, 2, 3, 4, 5]}
*/
@PostMapping("/batch")
public R<List<User>> batchQueryUsers(@RequestBody @Valid BatchQueryDTO dto) {
List<Long> ids = dto.getIds();
// 校验
if (CollectionUtils.isEmpty(ids)) {
return R.fail("请至少选择一个用户");
}
// 限制数量
if (ids.size() > 1000) {
return R.fail("一次最多查询1000个用户");
}
// 执行查询
List<User> users = userService.getUsersByIds(ids);
return R.ok(users);
}
}
控制台日志验证
-- 当传入 ids=[1, 2, 3] 时,实际生成的 SQL: SELECT id, name, age FROM user WHERE id IN (1, 2, 3) -- 查询结果:3 条记录
⑦ 避坑 & 最佳实践
❌ 常见错误
错误 1:想传 Collection 但不知道传什么
java
// ❌ 错误:Collection 是接口,不能 new
Collection<Long> ids = new Collection<>(); // 编译错误!
// ✅ 正确:传 List(ArrayList 是最常用的实现类)
List<Long> ids = new ArrayList<>();
错误 2:没做空值校验就调用
java
// ❌ 错误:前端可能传空数组
List<Long> ids = dto.getIds(); // 可能是空列表
return userMapper.selectBatchIds(ids);
// → SQL: WHERE id IN () → 报错!
// ✅ 正确:空值校验
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
错误 3:字符串分割时没处理空格
java
// ❌ 错误:前端传 "1, 2, 3"(有空格)会抛 NumberFormatException
Arrays.stream("1, 2, 3".split(","))
.map(Long::parseLong) // " 2" 无法解析!
// ✅ 正确:trim 后再解析
Arrays.stream("1, 2, 3".split(","))
.map(String::trim) // 先去掉空格
.map(Long::parseLong) // 再解析
✅ 最佳实践清单
| 实践 | 说明 |
|---|---|
| 优先用 List | 99% 场景传 List<Long> / List<Integer> / List<String> |
| 空值校验 | 调用前判断 null 或 isEmpty() |
| ID 数量控制 | 单次 IN 查询建议 ≤ 1000 条 |
| 字符串分割加 trim | 防止前端传参带空格导致解析异常 |
| 类型匹配 | 确保泛型类型实现了 Serializable(Long/Integer/String 都满足) |
⑧ 总结 & 路线图
记住了什么?
| 知识点 | 一句话总结 |
|---|---|
| 参数传什么 | 传 List<Long> / List<Integer> / List<String> |
| 底层原理 | List 实现了 Collection,基本类型实现了 Serializable |
| 前端传参 | JSON 数组(POST)或逗号字符串(GET) |
| 空值校验 | 必须做,否则 WHERE id IN () 报错 |
| 分批查询 | ID 超过 1000 时用 Guava Lists.partition 分批 |
下一步去哪?
-
🔗 MyBatis-Plus 条件构造器 :
QueryWrapper/LambdaQueryWrapper复杂查询 -
🔗 Page 分页查询 :
selectPage方法使用 -
🔗 批量插入:MyBatis-Plus 批量插入的性能优化