0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

SpringBoot+Mybatis如何实现流式查询?

jf_ro2CN3Fa 来源:CSDN 2023-06-12 09:57 次阅读

什么是mybatis流式查询?

使用mybatis作为持久层的框架时,通过mybatis执行查询数据的请求执行成功后,mybatis返回的结果集不是一个集合或对象,而是一个迭代器,可以通过遍历迭代器来取出结果集,避免一次性取出大量的数据而占用太多的内存。

Cursor

org.apache.ibatis.cursor.Cursor接口有三个抽象方法,分别是

isOpen() :判断cursor是否正处于打开状态;

isConsumed() :判断查询结果是否全部读取完;

getCurrentIndex() :查询已读取数据在全部数据里的索引位置;

publicinterfaceCursorextendsCloseable,Iterable{
//判断cursor是否正处于打开状态
//当返回true,则表示cursor已经开始从数据库里刷新数据了;
booleanisOpen();
//判断查询结果是否全部读取完;
//当返回true,则表示查询sql匹配的全部数据都消费完了;
booleanisConsumed();
//查询已读取数据在全部数据里的索引位置;
//第一条数据的索引位置为0;当返回索引位置为-1时,则表示已经没有数据可以读取;
intgetCurrentIndex();
}

代码实现

mybatis的所谓流式查询,就是服务端程序查询数据的过程中,与远程数据库一直保持连接,不断的去数据库拉取数据,提交事务并关闭sqlsession后,数据库连接断开,停止数据拉取,需要注意的是使用这种方式,需要自己手动维护sqlsession和事务的提交。

1、实现方式很简单,原来返回的类型是集合或对象,流式查询返回的的类型Curor,泛型内表示实际的类型,其他没有变化;

@Mapper
publicinterfacePersonDao{
CursorselectByCursor();
IntegerqueryCount();

}

select*fromsys_personorderbyiddesc


selectcount(*)fromsys_person

2、dao层向service层返回的是Cursor类型对象,只要不提交关闭sqlsession,服务端程序就可以一直从数据数据库读取数据,直到查询sql匹配到数据全部读取完;

示例里的主要业务逻辑是:从sys_person表中读取所有的人员信息数据,然后按照每1000条数据为一组,读取到内存里进行处理,以此类推,直到查询sql匹配到数据全部处理完,再提交事务,关闭sqlSession;

@Service
@Slf4j
publicclassPersonServiceImplimplementsIPersonService{
@Autowired
privateSqlSessionFactorysqlSessionFactory;

@Override
publicvoidgetOneByAsync()throwsInterruptedException{
newThread(newRunnable(){
@SneakyThrows
@Override
publicvoidrun(){
//使用sqlSessionFactory打开一个sqlSession,在没有读取完数据之前不要提交事务或关闭sqlSession
log.info("----开启sqlSession");
SqlSessionsqlSession=sqlSessionFactory.openSession();
try{
//获取到指定mapper
PersonDaomapper=sqlSession.getMapper(PersonDao.class);
//调用指定mapper的方法,返回一个cursor
Cursorcursor=mapper.selectByCursor();
//查询数据总量
Integertotal=mapper.queryCount();
//定义一个list,用来从cursor中读取数据,每读取够1000条的时候,开始处理这批数据;
//当前批数据处理完之后,清空list,准备接收下一批次数据;直到大量的数据全部处理完;
ListpersonList=newArrayList<>();
inti=0;
if(cursor!=null){
for(Personperson:cursor){
if(personList.size()< 1000) {
//                            log.info("----id:{},userName:{}", person.getId(), person.getUserName());
                                 personList.add(person);
                             } else if (personList.size() == 1000) {
                                 ++i;
                                 log.info("----{}、从cursor取数据达到1000条,开始处理数据", i);
                                 log.info("----处理数据中...");
                                 Thread.sleep(1000);//休眠1s模拟处理数据需要消耗的时间;
                                 log.info("----{}、从cursor中取出的1000条数据已经处理完毕", i);
                                 personList.clear();
                                 personList.add(person);
                             }
                             if (total == (cursor.getCurrentIndex() + 1)) {
                                 ++i;
                                 log.info("----{}、从cursor取数据达到1000条,开始处理数据", i);
                                 log.info("----处理数据中...");
                                 Thread.sleep(1000);//休眠1s模拟处理数据需要消耗的时间;
                                 log.info("----{}、从cursor中取出的1000条数据已经处理完毕", i);
                                 personList.clear();
                             }
                         }
                         if (cursor.isConsumed()) {
                             log.info("----查询sql匹配中的数据已经消费完毕!");
                         }
                     }
                     sqlSession.commit();
                     log.info("----提交事务");
                 }catch (Exception e){
                     e.printStackTrace();
                     sqlSession.rollback();
                 }
                 finally {
                     if (sqlSession != null) {
                         //全部数据读取并且做好其他业务操作之后,提交事务并关闭连接;
                         sqlSession.close();
                         log.info("----关闭sqlSession");  
                     }
                 }
                
            }
        }).start();
    }
}

应用场景

其实mybatis的流式查询适用范围很有限,这里举个例子,假如有这样一个需求 :有50万员工的一年的工资数据明细,需要输出一张公司支出工资的数据报表。

需求很简单,估计有人是这样想:这太简单了,查询出员工的工资数据明细,然后按照套上公式逐条计算出结果,然后汇总计算结果,插入到新的结果表里不就行了。事实上这件事绝对不简单:

50万的数据全部读取到jvm的内存里得占用多大空间?

这么多对象的垃圾回收又需要多久?

这么多数据计算是高频行为还是低步行为?

如果计算到某条员工的数据发生异常,已经计算好的数据要不要全部回滚?...

总之,直接取出50万数据来计算,风险肯定不小。那怎么办呢?

在实际的开发中,也经常遇到一些百十万,说大不大,说小不小的数据报表处理,我的主要设计思路通常就是数据切隔+异步,具体怎么做呢?结合上面的例子,是这样的:

1、按照月份、省份或者部门,对工资明细数据进行数据切隔分组;

2、把不同月份、省份、部门的工资数据包装成多线程任务,放到线程池中去执行;

3、根据切隔的多线程任务数量,定义一个同步工具类CountDownLatch;

4、根据同步工具类CountDownLatch,来判断所有的多线程任务是否全部执行完;等到所有的多线程任务全部执行完成后,再执行汇总的逻辑;

5、在多线程任务里,查询具体月份、省份的员工工资数据明细的时候,如果数据量还是不少,就可以使用mybatis的流式查询,分批获取员工工资明细数据,进行当前批的计算、汇总,然后所有分批数据都计算完成后,再汇总所有分批数据;

注意事项

mybatis的流式查询的本意,是避免大量数据的查询而导致内存溢出,因此dao层查询返回的是一个迭代器(Cursor),可以每次从迭代器中取出一条查询结果,在实际业务开发过程中,即是根据实际的jvm内存大小,从迭代器中取出一定数量的数据后,再进行数据处理,待处理完之后,继续取出一定数据再处理,以此类推直到全部数据处理完,这样做的最大好处就是能够降低内存使用和垃圾回收器的负担,使数据处理的过程相对更加高效、可控,内存溢出的风险较小;

好处很明显,缺点也很就明显,处理的时间可能会变长,需要引入多线程异步操作,并且在迭代器遍历和数据处理的过程中,数据库连接不能断开,即当前sqlSession要保持持续打开状态,一量断开,数据读取就会中断,所以关于这块的处理,使用mybatis原生的sqlSession进行手动查询、提交事务、回滚和关闭sqlSession最为稳妥、最简单。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 迭代器
    +关注

    关注

    0

    文章

    43

    浏览量

    4302
  • SpringBoot
    +关注

    关注

    0

    文章

    173

    浏览量

    167

原文标题:SpringBoot+Mybatis 如何实现流式查询,你知道吗?

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    一文了解MyBatis查询原理

    本文通过MyBatis一个低版本的bug(3.4.5之前的版本)入手,分析MyBatis的一次完整的查询流程,从配置文件的解析到一个查询的完整执行过程详细解读
    的头像 发表于 10-10 11:42 1400次阅读

    SpringBoot配置Mybatis的2个错误和修正

    SpringBoot】配置Mybatis错误
    发表于 04-19 10:31

    在pom.xml中增加mybatis-generator相关配置的步骤

    springboot配置mybatis-generator生成mybatis相关接口、xml文件、和实体类
    发表于 05-08 17:04

    基于SpringBoot mybatis方式的增删改查实现

    SpringBoot mybatis方式实现增删改查
    发表于 06-18 16:56

    数据库整合Mybatis框架

    微服务 SpringBoot 20(九):整合Mybatis
    发表于 07-16 11:03

    mybatis的最简两种模式

    【本人秃顶程序员】springboot专辑:如何优雅的使用mybatis
    发表于 10-23 09:01

    MyBatis的整合

    SpringBoot-15-之整合MyBatis-注解篇+分页
    发表于 10-28 08:09

    基于java springboot电影购票小程序源码相关资料推荐

    后台springboot mybatis 前端微信小程序电影院订票系统用户信息管理:用户注册后,可修改个人信息、登录密码等影片分类:对影片进行分类,按类型、国家区域分类,有筛选功能影片的信息:(影片
    发表于 12-30 06:14

    MyBatis实现原理

    本文主要详细介绍了MyBatis实现原理。mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSession Executor
    的头像 发表于 02-24 11:25 6459次阅读
    <b class='flag-5'>MyBatis</b>的<b class='flag-5'>实现</b>原理

    MyBatis流式查询轻松帮你解决分页慢的问题

    作者丨捏造的信仰 segmentfault.com/a/1190000022478915 Part1基本概念 流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条
    的头像 发表于 08-04 15:52 4155次阅读

    Fluent Mybatis、原生MybatisMybatis Plus对比

    使用fluent mybatis可以不用写具体的xml文件,通过java api可以构造出比较复杂的业务sql语句,做到代码逻辑和sql逻辑的合一。不再需要在Dao中组装查询或更新操作,在xml或
    的头像 发表于 09-15 15:41 1396次阅读

    源码学习之MyBatis的底层查询原理

    本文通过MyBatis一个低版本的bug(3.4.5之前的版本)入手,分析MyBatis的一次完整的查询流程,从配置文件的解析到一个查询的完整执行过程详细解读
    的头像 发表于 10-10 11:42 753次阅读

    SpringBoot+ElasticSearch实现模糊查询功能

    基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
    的头像 发表于 12-30 14:00 953次阅读

    MyBatis-Plus为什么不支持联表

    `的所有功能`MyBatis Plus Join`同样拥有;框架的使用方式和`MyBatis Plus`一样简单,几行代码就能实现联表查询的功能
    的头像 发表于 02-28 15:19 2400次阅读
    <b class='flag-5'>MyBatis</b>-Plus为什么不支持联表

    SpringBoot实现动态切换数据源

    最近在做业务需求时,需要从不同的数据库中获取数据然后写入到当前数据库中,因此涉及到切换数据源问题。本来想着使用Mybatis-plus中提供的动态数据源SpringBoot的starter:dynamic-datasource-spring-boot-starter来
    的头像 发表于 12-08 10:53 976次阅读
    <b class='flag-5'>SpringBoot</b><b class='flag-5'>实现</b>动态切换数据源