这个问题结合你前面的 MySQL 索引话题,大概率是在说 MyBatis Mapper 查询结果的处理方式。我按使用场景从常见到高级给你梳理:
一、最常见:Mapper 返回 List<T> + 业务层循环
1️⃣ foreach(增强 for 循环)
List<InfraApiAccessLog> list = infraApiAccessLogMapper.selectByCondition(param);for (InfraApiAccessLog log : list) {// 逐条处理System.out.println(log.getOperateName());}
2️⃣ 传统 for 循环
List<InfraApiAccessLog> list = mapper.selectByCondition(param);for (int i = 0; i < list.size(); i++) {InfraApiAccessLog log = list.get(i);// 处理,需要下标时用这个}
3️⃣ Iterator 迭代器
Iterator<InfraApiAccessLog> it = list.iterator();while (it.hasNext()) {InfraApiAccessLog log = it.next();// 处理}
4️⃣ Java 8 Stream API
list.forEach(log -> {// 处理});// 或list.stream().filter(log -> "下载文件".equals(log.getOperateName())).forEach(log -> { /* 处理 */ });
二、大数据量:游标(Cursor)流式读取
当结果集几十万/上百万行时,List 方式会 OOM,用 Cursor:
5️⃣ Cursor<T> + foreach(MyBatis 3.4+)
// Mapper 接口@Select("SELECT * FROM infra_api_access_log WHERE create_time > #{start}")Cursor<InfraApiAccessLog> selectByCursor(@Param("start") String start);
// 调用处try (Cursor<InfraApiAccessLog> cursor = mapper.selectByCursor("2026-06-20")) {for (InfraApiAccessLog log : cursor) {// 逐行读取,内存中只保留当前行System.out.println(log);}}// 自动关闭
⚠️ 注意:Cursor 必须在 同一个 SqlSession 生命周期内 遍历,不能在方法返回后遍历(除非用
SqlSessionTemplate的流式查询)。
三、最底层:ResultHandler 回调
6️⃣ ResultHandler 逐行处理(最省内存)
// Mapper 接口@Select("SELECT * FROM infra_api_access_log WHERE create_time > #{start}")void selectByResultHandler(@Param("start") String start, ResultHandler<InfraApiAccessLog> handler);
// 调用处mapper.selectByResultHandler("2026-06-20", context -> {InfraApiAccessLog log = (InfraApiAccessLog) context.getResultObject();// 逐行处理,不会一次性加载到内存System.out.println(log.getOperateName());});
底层原理:MyBatis 每从 JDBC ResultSet 读出一行就回调一次,完全不缓存结果集。
四、分页循环(经典模式)
7️⃣ PageHelper / MyBatis-Plus 分页 + 循环
int pageNum = 1;int pageSize = 1000;while (true) {PageHelper.startPage(pageNum, pageSize);List<InfraApiAccessLog> list = mapper.selectByCondition(param);if (list.isEmpty()) break;for (InfraApiAccessLog log : list) {// 处理}pageNum++;}
或 MyBatis-Plus 的 Page 对象:
Page<InfraApiAccessLog> page = new Page<>(1, 1000);while (true) {page = mapper.selectPage(page, wrapper);if (page.getRecords().isEmpty()) break;// 处理 recordspage.setCurrent(page.getCurrent() + 1);}
五、XML 里的 <foreach>(容易混淆)
8️⃣ MyBatis <foreach> 标签
这个是拼接 SQL 用的,不是读取结果:
<select id="selectByIds" resultType="InfraApiAccessLog">SELECT * FROM infra_api_access_logWHERE id IN<foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach></select>
它生成的是
WHERE id IN (1,2,3,...)这种 SQL,不是遍历结果集。
六、对比总结
| 方式 | 内存占用 | 适用场景 | 复杂度 |
|---|---|---|---|
List<T> + foreach |
高(全量加载) | 小结果集(几百~几万) | ⭐ 最简单 |
Stream 处理 List |
高(全量加载) | 需要过滤/转换的小结果集 | ⭐⭐ |
Cursor<T> + foreach |
低(逐行) | 大结果集,需遍历 | ⭐⭐⭐ |
ResultHandler |
最低(逐行回调) | 超大结果集,流式处理 | ⭐⭐⭐⭐ |
| 分页循环 | 低(每页加载) | 大结果集 + 需要更新/删除 | ⭐⭐⭐ |
💡 选择建议
结果集 < 1万行 → 直接 List + foreach ✅结果集 1万~50万行 → 分页循环 或 Cursor ✅结果集 > 50万行 → Cursor 或 ResultHandler ✅需要边读边更新 → 分页循环(避免游标期间锁表太久)✅
