stream流中的坑,peek/map/filter

起因

所在系统为一个对账系统,涉及的业务为发布账单,数据结构定的是供应商账单发布,生成企业账单和个人账单。发布账单处理完本系统业务后,需要生成站内通知和调用外部接口生成短信通知。后来增加需求,需要在发布完成后调用外部接口向总平台发送对应人员的待办。看着是不是还行,逻辑上没什么问题,磨人的地方就在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());

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Qt 学习笔记】Day1 | Qt 开发环境的搭建

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Day1 | Qt 开发环境的搭建 文章编号&#xff1a;Qt 学习笔记 / 02 文…

C++初学者:优雅创建第一个窗口

我想学习C做一些实用的程序&#xff0c;但是我不想在软件界面上花太多的时间&#xff0c;可是每每就是界面影响我的思绪。 今天学习C类的包装知识&#xff0c;终于整出了一个我的界面类&#xff0c;虽然封装水平很弱&#xff0c; 这次就用这个类&#xff0c;写了自己工作上常用…

Node.js中Router的使用

文章目录 介绍router的优点1.导入Express和创建Router&#xff1a;2. 定义路由&#xff1a;3.将router暴露到模块外&#xff1a;4. 将Router挂载到Express应用中&#xff1a;4.1.引入router4.2.使用中间件让router在Express应用中生效(三种写法) 5. 完整示例&#xff1a;5.1.编…

Vue3+Vite Nginx部署 跨域

打包项目 webstorm打开项目之后&#xff0c;在Terminal执行打包命令 pnpm run build:prod 复制到Nginx 打包完成之后,生成的包在根目录dist&#xff0c;把dist目录拷贝到Nginx放网站目录下&#xff1a;\nginx-1.25.2\html\divided &#xff0c;dist改名了divided 修改配置…

【JavaSE】内部类

目录 前言 内部类 内部类的种类 1. 实例内部类 2 静态内部类 3 匿名内部类 4 局部内部类 结语 前言 内部类是我们前面学习遗留下来的知识点&#xff0c;在学完接口后才能更好的理解它&#xff0c;因此等到现在才讲 内部类 在Java中&#xff0c;我们可以将A类定义在B…

短视频素材哪里去找?五大网站助你轻松解决素材难题!

你好&#xff0c;短视频小能手们&#xff0c;是不是经常在为找不到好看的视频素材而烦恼&#xff1f;不用怕&#xff0c;今天我要为你们揭秘五个超赞的视频素材网站&#xff0c;让你的视频素材&#xff0c;制作事半功倍&#xff0c;轻松赢得点赞和关注&#xff01;瞬间成为热门…

关于Windows中AppData的相关知识,看这篇文章就可以了

如果AppData文件夹占用了你电脑上的太多空间,则需要清理AppData文件夹。下面是一些帮助你在Windows计算机上进行AppData清理的方法。 什么是AppData文件夹 AppData文件夹是保存应用程序数据和设置的位置。每个Windows计算机在C驱动器上都有一个AppData文件夹。AppData文件夹…

自己动手用ESP32手搓一个智能机器人:ESP32-CAM AI Robot

目录 介绍 硬件需求 软件需求 步骤 总结 源码下载 介绍 ESP32-CAM是一款集成了Wi-Fi和蓝牙功能的微控制器模块&#xff0c;同时还集成了摄像头接口&#xff0c;使其成为一个非常适合构建智能机器人的选择。在本项目中&#xff0c;我将向您展示如何使用ESP32-CAM模块构建…

C# winform校验文件版本差异及版本号

界面 代码 using System.Diagnostics;namespace VersionTool {public partial class Form1 : Form{List<string> fileNmaes new List<string>() { "PhotoMes.Base.dll", "PhotoMes.App.exe", "PhotoMes.Cameras.dll" };public F…

JavaScript高级 —— 学习(二)

一、深入对象 &#xff08;一&#xff09;创建对象三种方式 1.利用对象字面量创建 <body><script>const obj {}</script> </body> 2.利用 new Object() 创建 <body><script>const obj new Object({uname: 一个人})console.log(obj)…

AcWing刷题(每日一题)-区间合并

挤牛奶 区间合并&#xff1a; &#xff08;写的有丢丢乱T_T&#xff09; from typing import List def merge(intervals: List[List[int]]) -> List[List[int]]:# 按照第一个元素从小到大进行排序intervals.sort(keylambda x: x[0])# 初始化一个新的数组new_list list()f…

《操作系统导论》第14章读书笔记:插叙:内存操作API

《操作系统导论》第14章读书笔记&#xff1a;插叙&#xff1a;内存操作API —— 杭州 2024-03-30 夜 文章目录 《操作系统导论》第14章读书笔记&#xff1a;插叙&#xff1a;内存操作API1.内存类型1.1.栈内存&#xff1a;它的申请和释放操作是编译器来隐式管理的&#xff0c;所…

Yolo 自制数据集dect训练改进

上一文请看 Yolo自制detect训练-CSDN博客 简介 如下图&#xff1a; 首先看一下每个图的含义 loss loss分为cls_loss, box_loss, obj_loss三部分。 cls_loss用于监督类别分类&#xff0c;计算锚框与对应的标定分类是否正确。 box_loss用于监督检测框的回归&#xff0c;预测框…

鸿蒙OS开发实战:【打造自己的搜索入口】

背景 几乎每家应用中都带有搜索功能&#xff0c;关于这个功能的页面不是特别复杂&#xff0c;但如果要追究其背后的一系列逻辑&#xff0c;可能是整个应用中最复杂的一个功能。今天主要实践目标&#xff0c;会抛开复杂的逻辑&#xff0c;尝试纯粹实现一个“搜索主页”&#xf…

STM32CubeIDE基础学习-USART串口通信实验(中断方式)

STM32CubeIDE基础学习-USART串口通信实验&#xff08;中断方式&#xff09; 文章目录 STM32CubeIDE基础学习-USART串口通信实验&#xff08;中断方式&#xff09;前言第1章 硬件介绍第2章 工程配置2.1 工程外设配置部分2.2 生成工程代码部分 第3章 代码编写第4章 实验现象总结 …

3D数据格式导出工具HOOPS Publish如何生成高质量3D PDF?

在当今数字化时代&#xff0c;从建筑设计到制造业&#xff0c;从医学领域到电子游戏开发&#xff0c;3D技术已经成为了不可或缺的一部分。在这个进程中&#xff0c;将3D模型导出为3D PDF格式具有重要的意义。同时&#xff0c;HOOPS Publish作为一个领先的解决方案&#xff0c;为…

Python算法学习

一、排序 排序算法是指将一组数据按照某种规则重新排列&#xff0c;使得数据呈现出递增或递减的顺序。常见的排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等。 1.冒泡排序 解释&#xff1a; 冒泡排序通过不断交换相邻两个元素的位置&#xff0c;使…

仓库规划(plan)

明天就要考试了&#xff0c;但是我正处于一点都不想学的状态 高考前我也是这样的 逆天 代码如下&#xff1a; #include<vector> #include<cstdio> using namespace std; int n, m; struct Node{int id;vector<int> d;bool operator<(const Node &t…

LInux|命令行参数|环境变量

LInux|命令行参数|环境变量 命令行参数main的参数之argc&#xff0c;argv几个小知识<font color#0099ff size 5 face"黑体">1.子进程默认能看到并访问父进程的数据<font color#4b0082 size 5 face"黑体">2.命令行创建的程序父进程都是bash 环…

命名空间【C++】(超详细)

文章目录 命名空间的概念命名空间的定义命名空间定义的位置作用域每一个命名空间都是一个独立的域作用域符&#xff1a;&#xff1a; 编译器找一个变量/函数等的定义&#xff0c;寻找域的顺序为什么要有命名空间&#xff1f;1.解决库与程序员定义的同名的重定义问题2.解决程序员…