stream流中的坑,peek/map/filter

news2024/11/27 14:47:19

起因

所在系统为一个对账系统,涉及的业务为发布账单,数据结构定的是供应商账单发布,生成企业账单和个人账单。发布账单处理完本系统业务后,需要生成站内通知和调用外部接口生成短信通知。后来增加需求,需要在发布完成后调用外部接口向总平台发送对应人员的待办。看着是不是还行,逻辑上没什么问题,磨人的地方就在stream流的各种处理时。
先简单介绍一下原代码,原代码中在处理最外层增加了事务保障,在处理完本系统的逻辑业务之后,使用publishEvent转而去发站内通知和短信通知。所以我是直接在publishEvent中去发待办。

事件的整个流程

在这里插入图片描述

处理过程中遇到的问题以及代码版本

版本一(此时未增加50个限制)

版本一代码
if (event instanceof BillPublishedEvent) {
            try {
                log.info("监听到账单发布事件,需要根据个人账单生成账单生成通知");
                BillSupplier billSupplier = ((BillPublishedEvent) event).getBillSupplier();

                List<BillPerson> billPersonList = billPersonService.list(new LambdaQueryWrapper<BillPerson>().eq(BillPerson::getBillSupplierGuid, billSupplier.getGuid()));
                if(billPersonList.size()>0){
                    List<Integer> personIdList = billPersonList.stream().map(BillPerson::getPersonId).collect(Collectors.toList());
                    List<String> personUserNameList = userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUserId, personIdList)).stream().map(User::getUserName).collect(Collectors.toList());
                    String modelId = CommUtils.makeAutoGUID();
                    Integer toDoResult = toDoMessageAppService.sendToDoMessage(modelId);
                    if(toDoResult.equals("200")){
                        boolean update = billPersonService.lambdaUpdate()
                                .set(BillPerson::getPublishToDo, modelId.concat("_10"))
                                .in(BillPerson::getGuid, billPersonList.stream().map(BillPerson::getGuid).collect(Collectors.toList()))
                                .update();
                        if(update){
                            log.info("{}的个人账单发布账单待办通知修改完成");
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
版本一遇到的问题(事务中的增删改查只会在整个处理完成后执行)

问题所在代码为:

问题是:别看执行完成了,返回的update为true,但其实根本就没有更新。原因是整个处理过程是在事务中。
经过这次我理解的事务中的任何修改、生成、删除等操作都只会在整个处理过程完成后执行,其余时间都是保存在某个地方。而下面这个更新实际是从数据库中获取再更新,那当然不可能更新成功。
当时发现是因为billPerson数据是需要insert进去的,但执行到这步,查看数据库时根本没数据。
boolean update = billPersonService.lambdaUpdate()
                                .set(BillPerson::getPublishToDo, modelId.concat("_10"))
                                .in(BillPerson::getGuid, billPersonList.stream().map(BillPerson::getGuid).collect(Collectors.toList()))
                                .update();
后修改为
if (event instanceof BillPublishedEvent) {
            try {
                log.info("监听到账单发布事件,需要根据个人账单生成账单生成通知");
                BillSupplier billSupplier = ((BillPublishedEvent) event).getBillSupplier();
                List<BillPerson> billPersonList = billPersonService.list(new LambdaQueryWrapper<BillPerson>().eq(BillPerson::getBillSupplierGuid, billSupplier.getGuid()));
                if (billPersonList.size() > 0) {
                    List<Integer> personIdList = billPersonList.stream().map(BillPerson::getPersonId).collect(Collectors.toList());
                    List<String> personUserNameList = userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUserId, personIdList)).stream().map(User::getUserName).collect(Collectors.toList());
                    String modelId = CommUtils.makeAutoGUID();
                    try {
                        Integer toDoResult = toDoMessageAppService.sendToDoMessage(personUserNameList,modelId);
                        if (toDoResult.equals("200")) {
                            String modelIdNew = modelId.concat("_10");
                            billPersonList = billPersonList.stream().peek(billPerson -> billPerson.setPublishToDo(modelIdNew)).collect(Collectors.toList());
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

版本二 (增加50个限制)

版本二代码
if (event instanceof BillPublishedEvent) {
            try {
                log.info("监听到账单发布事件,需要根据个人账单生成账单生成通知");
                BillSupplier billSupplier = ((BillPublishedEvent) event).getBillSupplier();
                List<BillPerson> billPersonList = billPersonService.list(new LambdaQueryWrapper<BillPerson>().eq(BillPerson::getBillSupplierGuid, billSupplier.getGuid()));
                if (billPersonList.size() > 0) {
                    List<Integer> personIdList = billPersonList.stream().map(BillPerson::getPersonId).collect(Collectors.toList());
                    List<String> personUserNameList = userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUserId, personIdList)).stream().map(User::getUserName).collect(Collectors.toList());
                    String modelId = CommUtils.makeAutoGUID();
                    while(!personUserNameList.isEmpty()){
                        // 此处截取50个手机号已发送待办逻辑是:
                        // 先取出前50个手机号发送待办,然后从原list中去除这50个手机号。
                        List<String> currentHandleList = personUserNameList.subList(0, Math.min(50, personUserNameList.size()));
                        try {
                            Integer toDoResult = toDoMessageAppService.sendToDoMessage(currentHandleList,modelId);
                            personUserNameList = personUserNameList.subList(50,personUserNameList.size());
                            if (toDoResult.equals("200")) {
                                String modelIdNew = modelId.concat("_10");
                                billPersonList = billPersonList.stream().peek(billPerson -> billPerson.setPublishToDo(modelIdNew)).collect(Collectors.toList());
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            continue;
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
版本二遇到的问题(循环中不可修改list)

问题所在代码为:

// 会出现 ConcurrentModificationException 异常,因为做了个蠢事,不可以在循环中删除List,因为循环本身是根据index定位的
List<String> currentHandleList = personUserNameList.subList(0, Math.min(50, personUserNameList.size()));
personUserNameList = personUserNameList.subList(50,personUserNameList.size());                
后修改为
                    int paramSize = 50;
                    for (int i = 0; i < personUserNameList.size(); i += paramSize) {
                        List<BillPerson> currentHandlePersonList = new ArrayList<>();
                        int endIndex = Math.min(i + paramSize, personUserNameList.size());
                        List<String> subList = personUserNameList.subList(i, endIndex);
                        // 后面跟处理
                    }

版本三 (真正的stream流坑)

版本三代码
if (event instanceof BillPublishedEvent) {
            try {
                log.info("监听到账单发布事件,需要根据个人账单生成账单生成通知");
                BillSupplier billSupplier = ((BillPublishedEvent) event).getBillSupplier();

                // 此处是为了向商网办公发送待办
                List<BillPerson> billPersonList = billPersonService.list(new LambdaQueryWrapper<BillPerson>().eq(BillPerson::getBillSupplierGuid, billSupplier.getGuid()));
                if (billPersonList.size() > 0) {
                    Map<Integer, String> map = new HashMap<>();  // key为personId或者说是userId,value为userName
                    List<Integer> personIdList = billPersonList.stream().map(BillPerson::getPersonId).collect(Collectors.toList());
                    // 因为billPerson表只有personId,user表才有userId和user手机号,这里将personId和手机号作为key-value写入map中了,以便50个手机号执行完后更新对应的个人账单
                    List<String> personUserNameList = userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getUserId, personIdList)).stream().map(user -> {
                        if (!map.containsKey(user.getUserId())) {
                            map.put(user.getUserId(), user.getUserName());
                        }
                        return user.getUserName();
                    }).collect(Collectors.toList());
                    String modelId = CommUtils.makeAutoGUID();
                    int paramSize = 50;

                    for (int i = 0; i < personUserNameList.size(); i += paramSize) {
                        int endIndex = Math.min(i + paramSize, personUserNameList.size());
                        List<String> subList = personUserNameList.subList(i, endIndex);
                        log.info("i为{},endIndex为{},subList为{}", i, endIndex, subList);
                        try {
                            Integer toDoResult = toDoMessageAppService.sendToDoMessage(subList, modelId);
                            // 这里要求休息一会,不然会被检测为爆破
                            Thread.sleep(3000);
                            
                            if (toDoResult.equals("200")) {
                                List<String> subListCopy = subList;
                                List<Integer> personidListHandled = map.entrySet().stream().filter(entry -> subListCopy.contains(entry.getValue())).map(Map.Entry::getKey).collect(Collectors.toList());
                                log.info("这次处理的用户对应的personId为{}", personidListHandled);
                                String modelIdNew = modelId.concat("_10");
                                // 找到全量billPersonList中这次处理的个人账单list,并修改某个值。
                                billPersonList = billPersonList.stream().filter(e -> personidListHandled.contains(e.getPersonId())).peek(billPerson -> billPerson.setPublishToDo(modelIdNew)).collect(Collectors.toList());
                                
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                            continue;
                        }
                    }
                } else {
                    // log.info("容器本身事件:" + event);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
版本三问题

问题所在代码为

此时,版本一中的问题重现了,因为个人账单中的那个字段根本就没有修改。
我可以理解filter筛选出来了,但接收时是使用的原list,按说在第一次循环有50条数据,但之后的循环就不会有数据能被筛选出来了
但是就不,偏偏数据都在,但是那个字段根本就没更新。
billPersonList = billPersonList.stream().filter(e -> personidListHandled.contains(e.getPersonId())).peek(billPerson -> billPerson.setPublishToDo(modelIdNew)).collect(Collectors.toList());
最终版
// 这里要用新list接收一下
currentHandlePersonList = billPersonList.stream().filter(e -> personidListHandled.contains(e.getPersonId())).peek(billPerson -> billPerson.setPublishToDo(modelIdNew)).collect(Collectors.toList());

List<BillPerson> targetPersonList = currentHandlePersonList;   // 这里需要再赋值一下,否则在下面在stream().filter时会报错
// 此处使用replaceAll替换,只要两者的某个条件相同,就用List2中的数据替换list1中的数据
billPersonList.replaceAll(e -> {
	List<BillPerson> currentHandlePersonListOne = 
	targetPersonList.stream().filter(tar -> tar.getGuid().equals(e.getGuid())).collect(Collectors.toList());
	if(currentHandlePersonListOne.size() >0){
      	return currentHandlePersonListOne.get(0);
	}
  	return e;
});

这次还学到

map中根据value值获取到对应的key值们

List<Integer> personidListHandled = map.entrySet().stream().filter(entry -> subListCopy.contains(entry.getValue())).map(Map.Entry::getKey).collect(Collectors.toList());

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1556598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D产品可视化SaaS

“我们正在走向衰退吗&#xff1f;” “我们已经陷入衰退了吗&#xff1f;” “我们正在步入衰退。” 过去几个月占据头条的问题和陈述引发了关于市场对每个行业影响的讨论和激烈辩论。 特别是对于科技行业来说&#xff0c;过去几周一直很动荡&#xff0c;围绕费用、增长和裁…

论文笔记:TALK LIKE A GRAPH: ENCODING GRAPHS FORLARGE LANGUAGE MODELS

ICLR 2024&#xff0c;reviewer评分 6666 1 intro 1.1 背景 当下LLM的限制 限制1&#xff1a;对非结构化文本的依赖 ——>模型有时会错过明显的逻辑推理或产生错误的结论限制2&#xff1a;LLMs本质上受到它们训练时间的限制&#xff0c;将“最新”信息纳入到不断变化的世…

[InternLM训练营第二期笔记]1. 书生·浦语大模型全链路开源开放体系

由于想学习一下LLM相关的知识&#xff0c;真好看到上海AI Lab举行的InternLM训练营&#xff0c;可以提高对于LLM的动手能力。 每次课堂都要求笔记&#xff0c;因此我就想在我的CSDN上更新一下&#xff0c;希望和感兴趣的同学共同学习~ 本次笔记是第一节课&#xff0c;介绍课。…

小白都能轻松上手的小程序发布教程

为了更好的让同学们学习怎么打包发行微信小程序的流程&#xff0c;我做了一个简单的关系图&#xff0c;方便同学了解uni-app还有开发者工具和微信公众号平台之间的关系 &#x1f60d;使用过Git的同学应该可以很快的理解&#xff0c;因为它的流程和Git有很多的相似点&#xff0c…

如何保证redis里的数据都是热点数据

MySQL 里有 2000w 数据&#xff0c;Redis 中只存 20w 的数据&#xff0c;如何保证 redis 中的数据都是热点数据&#xff1f; 1.Redis 过期删除策略 1&#xff09;惰性删除:放任键过期不管&#xff0c;但是每次从键空间中获取键时&#xff0c;都检查取得的键是否过期&#xff0c…

腾讯 tendis 替代 redis linux安装使用

下载地址 Tendis存储版 点击下载 linux 解压 tar -zxvf 安装包.tgz cd 解压安装包/scripts 启动 ./start.sh 停止 ./stop.sh 详细配置 修改 /scripts tendisplus.conf # tendisplus configuration for testing # 绑定本机IIP bind 192.168.31.112 port 51002 #设…

gitee拉取与推送

&#x1f331;博客主页&#xff1a;青竹雾色间 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 目录 一&#xff0c;从本地推送项目到gitee1.首先我们在gitee上创建一个仓库2.clone远程仓库到本地3.git的三板斧3.1. add - 将代码添加到本地仓库3.2. commit …

【C++】常对象

目录 常对象常对象特点常数据成员常成员函数对象的常引用 常对象 把对象定义为常对象&#xff0c;对象中的数据成员就是常变量&#xff0c;在定义时必须带实参&#xff08;或者有缺省构造函数&#xff09;作为数据成员的初值。 const Person p1(3,4);//定义了一个常对象常对象特…

蓝桥杯第十五届抱佛脚(五)DFS、BFS及IDS

蓝桥杯第十五届抱佛脚&#xff08;五&#xff09;DFS、BFS及IDS 深度优先搜索 DFS(Depth-First Search)即深度优先搜索,是一种用于遍历或搜索树或图的算法。它从根节点开始,尽可能沿着每一条路径直到这条路径最后一个节点被访问了,然后回退,继续访问下一条路径。它的基本思想…

【lrzsz】linux上lrzsz的安装和使用

一、lrzsz简介 rz&#xff0c;sz是Linux/Unix同Windows进行ZModem文件传输的命令行工具 rz 可以很方便的从客户端传文件到服务器&#xff1b; sz也可以很方便的从服务器传文件到客户端&#xff1b; 就算中间隔着跳板机也不影响。 rz(receive Zmodem) sz(send Zmodem) 远程…

在linux系统上部署脚本并设置定时执行

第一次在公司的服务器上部署了脚本&#xff0c;并且定时执行成功了&#xff0c;记录一下 首先在服务器上编写好python脚本 编写完成后&#xff0c;编写一个shell脚本&#xff0c;在其中设置文件的执行顺序 shell脚本内容如下 # 执行query_problematic_data文件 /bin/python3…

对iOS的内存存储的一些理解

最近写项目的时候遇到了一些内存上的问题&#xff08;比如内存泄漏等等&#xff09;&#xff0c;通过网上的方法解决后&#xff0c;好奇iOS的数据是如何存储的&#xff0c;特记于此。 一、iOS的内存区域 iOS 中应用程序使用的计算机内存不是统一分配空间&#xff0c;运行代码使…

OpenHarmony系统开发之应用接口文件转换工具介绍

简介&#xff1a; 应用接口文件转换工具是根据异构格式接口文件(.h 文件)转换生成 OpenHarmony 系统应用层需要的 TS(type-script)接口文件(*.d.ts)的工具。若某个服务实现方式为 c&#xff0c;且供应用层访问的接口已在.h 文件中定义&#xff0c;此时&#xff0c;NAPI 接口开…

JavaScript之Class构造及继承的底层实现原理

笔者语 已经坚持发布技术文章一个月&#xff0c;得到了一些朋友的阅读与支持&#xff0c;我感到很荣幸&#xff0c;也是继续坚持下去的动力。工作很多年&#xff0c;今年才开始写技术类文章发表&#xff0c;因为以前总是担心写错&#xff0c;把错误的知识带给别人&#xff0c;对…

Docker 部署 FRP 内网穿透 实现端口映射

Frp 是一个专注于内网穿透的高性能的反向代理应用&#xff0c;支持 TCP、UDP、HTTP、HTTPS 等多种协议&#xff0c;且支持 P2P 通信。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 官网地址&#xff1a;https://github.com/fatedier/frp 准备工作…

GeometryInstance点击改变颜色

目录 项目地址实现效果核心代码 项目地址 https://github.com/zhengjie9510/webgis-demo 实现效果 核心代码 // Draw different instances each with a unique color const rectangleInstance new Cesium.GeometryInstance({geometry: new Cesium.RectangleGeometry({recta…

学习鸿蒙基础(10)

目录 一、轮播组件 Swiper 二、列表-List 1、简单的List 2、嵌套的List 三、Tabs容器组件 1、系统自带tabs案例 2、自定义导航栏&#xff1a; 一、轮播组件 Swiper Entry Component struct PageSwiper {State message: string Hello Worldprivate SwCon: SwiperControl…

Kubernetes篇(一)— kubernetes介绍

目录 前言一、应用部署方式演变二、kubernetes简介三、kubernetes组件四、kubernetes概念 前言 本章节主要介绍应用程序在服务器上部署方式演变以及kubernetes的概念、组件和工作原理。 一、应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(2)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 初级教程素材 等文件 https://www.alipan.com/s/fC…

哔哩哔哩直播姬有线投屏教程

1 打开哔哩哔哩直播姬客户端并登录(按下图进行操作) 2 用usb连接电脑(若跳出安装驱动的弹窗点击确定或允许),usb的连接方式为仅充电 不要更改usb的连接方式(不然电脑会死机需要重启),此时电脑识别不到该手机设备(因为电脑把它识别为投屏设备) 想要正常连接电脑进行文件传输就按…