温馨提示:在 ChatGPT 官网(www.chatgpt.com)使用 GPT-5.5、ChatGPT-Image-2 等模型时,需要 ChatGPT Plus 或更高等级的会员权限。如需购买账号或充值会员,请扫码添加我们客服咨询。
基于您提供的内容,生成的摘要如下:,Mybatis Cursor分页是一种高效的流式读取方案,可避免一次性加载大量数据导致内存溢出,与传统分页不同,Cursor不会将全部数据加载到内存,而是通过数据库游标逐条或批量读取记录,实现“边读边处理”,开发者只需在Mapper接口中返回Cursor类型,并配合@Options设置fetchSize即可,使用时需注意在事务中执行,处理完成后及时关闭游标,这种方式尤其适合百万级数据导出或全表扫描场景,能有效降低内存开销,小白也能轻松掌握。
你好,我是今天来帮你解决一个麻烦问题的助手,很多刚学编程的朋友,在做查询的时候,都会遇到一个头疼的事:数据太多。
你要从数据库里查十万条记录,然后用程序一条条处理,我们常用的那个“分页查询”方式,就像去图书馆借书,管理员一次性把十万本书都搬到你面前,然后你还要在这堆书里一本本看,这就会带来两个问题,第一,你的电脑内存可能不够用,会卡死,第二,你等待管理员搬书的时间会很长,程序一直等在那里,什么事都干不了。
今天要讲的Mybatis Cursor,就是来解决这个问题的,它不叫“分页”,应该叫“流式读取”,它的想法很简单,用一点,拿一点”,还是那个图书馆的例子,Cursor的方式就像你站在图书馆的书架前,拿一本看一本,看完一本,再伸手拿下一本,你的手里永远只有一本书,这样,即使图书馆里有十万本书,你的口袋也永远不鼓。
我们得先明白,为什么之前那种分页(比如用PageHelper插件)不好?之前的分页,其实是“假分页”,它在数据库里查出所有数据,然后用一个叫LIMIT的指令,截取其中的一小段给你看,因为数据库在查询的时候,可能已经把所有符合条件的数据都找出来了,只不过最后只给你返回了那一小段,当你查询的总数据量特别特别大的时候,数据库查询那一瞬间的压力还是很大。
而Mybatis Cursor不一样,它是一个“双向通道”(或者说一个指针),我们告诉数据库:“嘿,我要开始查了,但你不用马上把结果都给我,你就跟我说第一行的数据在哪里,然后我读一条,你再给我下一条。” 这样,数据库不用一次性把所有结果都算出来,也不必一次性把数据通过网络传给我们的程序,这就像用一根水管接水,水龙头不用一下把整个水库的水都喷出来,只需要让水一小股一小股地流出来就行。
我们来看一个很简单的例子,假设我们有一个用户表,里面有20万个用户,我们要遍历这些用户,给每个用户发一条短信(只是假设,不要真发),如果用老办法,可能会这样写:
List<User> userList = userMapper.getAllUser();
for (User user : userList) {
// 这里发短信,或者做别的处理
sendMessage(user);
}
这段代码要是跑起来,如果你的电脑内存是16G,那可能没事,但如果数据量是两百万,或者用户对象里有很多大字段(比如头像图片的base64码),那么你的程序很可能就直接“Out of Memory”了,也就是内存溢出,程序崩掉。
现在我们换成Cursor,我们需要修改一下Mapper接口和对应的XML文件。
第一步:修改Mapper接口
以前你的接口可能是这样:
List<User> getAllUser();
现在改成这样:
Cursor<User> getAllUser();
看到了吗?返回值从List变成了Cursor,这里要注意,别忘了导入包:
import org.apache.ibatis.cursor.Cursor;
第二步:修改XML文件
对应的查询语句,几乎不用改。
<select id="getAllUser" resultType="com.example.model.User">
select * from user
</select>
有的朋友可能会问:“那我不写limit了?我一次性查全表,用Cursor还能快吗?”
这个我们要说清楚,用Cursor,并不是让这条SQL语句跑得更快,它只是让数据“流”出来,而不是“倒”出来,如果你只是想要“看”第一页的20条数据,用老的分页方式查询更快,因为数据库只返回20条,而Cursor是帮你处理“需要把所有数据都一次性处理一遍”的场景。
第三步:怎么用这个Cursor
获得Cursor对象之后,我们不能直接拿去用for each循环遍历,因为Cursor是一个可迭代的对象,但它需要在一个事务里使用,简单说,就是我们要在数据库连接还没关掉的时候,一条条拿数据。
正确的用法是这样的:
// 这里要使用事务,很多框架(比如Spring)默认只在一个方法里保持事务。
// 我们这里用一个简单的例子,假设我们在一个开启事务的服务方法里。
try (SqlSession session = sqlSessionFactory.openSession()) {
// 获取Mapper
UserMapper mapper = session.getMapper(UserMapper.class);
// 获取游标,注意:此时还没有获取任何一条数据
Cursor<User> cursor = mapper.getAllUser();
// 然后我们可以用迭代的方式,一条一条处理
for (User user : cursor) {
// 这里只处理一条数据
sendMessage(user);
// 处理完这一条,内存里就释放这一条
// cursor会自动帮我们拿取下一条
}
}
这里有一个非常重要的部分,就是那个try-with-resources,我们用了try (SqlSession session = ...),这样当循环结束的时候,SqlSession会自动关闭,数据库连接也会返回连接池。
第四步:注意“唯一”的坑
新手用Cursor最容易遇到的一个坑是:连接过早关闭。
如果你把cursor对象返回给了Controller层,然后尝试在Controller里用for循环去遍历它,那很可能会报错,因为你的Service方法结束了,事务提交了,数据库连接也关了,这时候cursor就成了一个“死游标”,里面没有数据,再读它会报错。
记住一个原则:在哪里打开连接,就在哪里读完数据,我们通常都是在Service层的方法里,拿到cursor,然后用循环把数据处理完,一定要在方法结束之前,把数据全部消费掉。
第五步:当你需要中途停止
我们不想读完所有数据,我们要查找第一个符合条件的用户,找到了就结束,Cursor也支持这个。
for (User user : cursor) {
if (user.getAge() > 20) {
// 找到第一个年龄大于20的用户
// 处理他
sendMessage(user);
// 跳出循环
break;
}
}
当你跳出循环后,Cursor还没有读完,但是因为你已经不需要了,那么SqlSession关闭后,这个Cursor也就没有用了,数据库那边也不会再继续传输数据,这样就很节省资源。
第六步:对比传统分页的“实际场景”
我们再来做个对比,让大家更清楚:
| 场景 | 传统分页 (List/PageHelper) | Cursor流式读取 |
|---|---|---|
| 查看数据报告 | 给用户展示第一页,点击下一页,这是完美的。 | 不适用,因为我们要的是页面展示。 |
| 导出全部数据到Excel | 如果数据量小,没问题,数据量大,内存飙升,容易崩溃。 | 完美,因为我们可以一条条写出到Excel文件,内存占用稳定。 |
| 数据处理任务(如批量更新) | 需要手动写limit循环,且每次查询都有数据库性能开销。 | 完美,只需要一次查询,流式读取,逐条更新。 |
| 搜索功能(比如搜“苹果”,只返回前10条) | 推荐使用,因为只需要少量数据。 | 用不上,因为Cursor是为处理全量数据设计的。 |
第七步:进阶小技巧:考虑数据库驱动版本
不同的数据库,对于Cursor的支持程度不一样,MySQL是支持的,但需要你使用的JDBC驱动版本支持流式读取,如果驱动版本太旧,你发现用了Cursor,但内存还是涨上去了,这可能是因为驱动没有真的开启流模式。
你可以在你的数据库连接URL里加上一个参数来强制开启:useCursorFetch=true。
jdbc:mysql://localhost:3306/db?useSSL=false&useCursorFetch=true
这个参数并不是所有数据库都适用,只是针对MySQL的一种常见优化。
- 是什么:Mybatis Cursor是一个游标,它允许你一条一条地从数据库拿数据,而不是一次把全部数据放在内存里。
- 什么时候用:当你需要处理大量数据(比如几万条、几十万条以上),并且需要一条条处理的时候用它,比如批量导出、数据清洗、定时任务。
- 什么时候不用:只是给用户看分页列表,比如后台管理系统的表格,用普通分页查询更简单、更快。
- 核心注意点:一定要在开启了事务的方法里使用cursor,并且在这个方法里把它消费完,不要把它返回给上一层。
- 内存友好:每次循环只处理一条数据,内存里只保留一条数据。
写到这里,你应该对Mybatis Cursor有了一个基本的认识,它不是一个很难的东西,它只是一个解决问题的思路:别贪心,一次只拿一点点。
当你下次再遇到一个可能把服务器内存撑爆的查询任务时,别怕,试试Cursor,它会像一个耐心的送报员,一张一张把报纸递给你,而不是把整个印刷厂给你搬过来。
希望这个教程对你有帮助,如果你在实际使用中遇到报错,别慌,先检查你的Mapper返回值是不是Cursor,检查你的事务有没有开启,检查你的循环是不是在事务方法内部,大部分问题都出在这三个地方。
温馨提示:在 ChatGPT 官网(www.chatgpt.com)使用 GPT-5.5、ChatGPT-Image-2 等模型时,需要 ChatGPT Plus 或更高等级的会员权限。如需购买账号或充值会员,请扫码添加我们客服咨询。


网友评论