浅论数据库聚合:合理使用LambdaQueryWrapper和XML

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、数据库聚合替代内存计算(关键优化)
  • 二、批量处理优化
  • 四、区域特殊处理解耦
  • 五、防御性编程增强


前言

技术认知点:使用 XML 编写 SQL 聚合查询并不会导致所有数据加载到内存,反而能 大幅减少内存占用并提升性能。

        LocalDateTime localDateTime = TimeUtilTool.startOfDay();LocalDateTime crossTime = LocalDateTime.now().minusDays(1);List<AAA> list = SERVICE1.list(new LambdaQueryWrapper<AAA>().between(AAA::GETTIME, localDateTime.minusDays(1), localDateTime));Map<String, List<AAA>> areaMap = list.stream().collect(Collectors.groupingBy(AAA::getAreaId));

一个对象占得内存很小,可能只有1kb;但是当一百万条时,数据量就达到了接近1个G,如果这时候处理数据,极易出现OOM;
应用层计算的劣势
GC压力:大量临时对象增加垃圾回收频率
多次遍历内存:stream().collect(groupingBy) 导致 O(n²) 时间复杂度
对象转换开销:MyBatis 将每条记录转换为 PO 对象消耗资源
全量数据加载:即使只需要统计值,仍需传输所有字段

所以要学习数据库聚合


原始代码分析

 @XxlJob("MethodDD")public void MethodDD(){LocalDateTime localDateTime = TimeUtilTool.startOfDay();LocalDateTime crossTime = LocalDateTime.now().minusDays(1);List<AAA> list = SERVICE1.list(new LambdaQueryWrapper<AAA>().between(AAA::GETTIME, localDateTime.minusDays(1), localDateTime));Map<String, List<AAA>> areaMap = list.stream().collect(Collectors.groupingBy(AAA::getAreaId));List<BBB> result = SAVEDATA(areaMap, crossTime);saveAreaStatisticsDaily(result, crossTime);}private List<BBB> SAVEDATA(Map<String, List<AAA>> areaMap, LocalDateTime crossTime) {List<CCCC> ccc = cacheTool.areaDictionary();List<BBB> result = new ArrayList<>();areaMap.forEach((areaId, areaList)->{BBB po = new BBB();Optional<CCCC> first = ccc.stream().filter(ccc -> ccc.getId().toString().equals(areaId)).findFirst();first.ifPresent(ccc -> {po.setAreaId(areaId);if(ccc.getId().toString().equals(areaId)){po.setAreaName(AreaNameBuilder.getAreaName(ccc));}Double carSpeed = 0.0;if (areaList == null || areaList.isEmpty()) {// 处理空列表的情况carSpeed = 0.0;} else {double totalSpeed = areaList.parallelStream()  .mapToDouble(AAA::getCarSpeed).sum();carSpeed = totalSpeed / areaList.size();}po.setMeanSpeed(new BigDecimal(carSpeed));po.setFlow(areaList.size());Map<String, List<AAA>> carTypeMap = areaList.stream().collect(Collectors.groupingBy(AAA::getCarType));carTypeMap.forEach((carType, carTypeList) ->{if (carType.equals("1")){po.setSmallCCCARFlow(carTypeList.size());} else if (carType.equals("2")){po.setMediumLargeBBBULLFlow(carTypeList.size());} else if (carType.equals("3")){po.setSmallMediumttttFlow(carTypeList.size());}else if (carType.equals("4")){po.setLargettttFlow(carTypeList.size());}else if (carType.equals("5")){po.setHazardousChemicalCCCARFlow(carTypeList.size());}else if (carType.equals("6")){po.setMotorcycle(carTypeList.size());}else if (carType.equals("7")){po.setOther(carTypeList.size());}});});po.setCrossTime(crossTime);result.add(po);statsService.save(po);});List<String> areaIds = areaMap.keySet().stream().toList();for (CCCC ccc : ccc) {if (!areaIds.contains(ccc.getId().toString())){BBB po = new BBB();po.setAreaId(ccc.getId().toString());po.setAreaName(AreaNameBuilder.getAreaName(ccc));po.setCrossTime(crossTime);result.add(po);statsService.save(po);}}return result;}

首先,用户有一个定时任务,每天凌晨统计卡口数据,并将结果保存到数据库。当前代码可能存在性能问题,尤其是当数据量大的时候,全量查询和处理会导致内存和性能问题。

  1. 全量数据加载到内存:使用trafficCCCARService.list查询所有符合条件的数据,如果数据量很大,会导致内存压力,甚至OOM。
  2. 多次遍历数据流:在处理每个区域的数据时,多次使用流操作进行分组和统计,可能导致性能下降。
  3. 频繁的数据库写入操作:在SAVEDATA方法中,每次处理一个区域就调用statsService.save(po),这样频繁的数据库插入操作效率低下。
  4. 硬编码的区域ID判断:在saveAreaStatisticsDaily方法中,直接判断特定的区域ID,这样的代码难以维护,且不符合面向对象的设计原则。

首先,全量数据的问题,可以考虑分页查询或者使用数据库的聚合功能,减少数据传输量。
其次,多次遍历数据流可以通过合并处理逻辑来减少遍历次数。
数据库写入操作应该批量进行,而不是逐条插入。
硬编码的问题可以通过枚举或配置来解决:代码中存在重复的区域ID判断,这部分应该抽象出来,使用更灵活的方式处理,比如使用Map来映射区域ID和对应的字段,避免大量的if-else语句。

一、数据库聚合替代内存计算(关键优化)

LambdaQueryWrapper和XML

  1. XML 只是定义 SQL 的方式:无论是 XML 还是 LambdaQueryWrapper,最终都会生成 SQL 发送到数据库执行
  2. 性能差异的根源:在于 SQL 本身的执行效率 和 数据传输量,而非 XML/Lambda 的代码形式

关键区别:

优化前(LambdaQueryWrapper):拉取全量原始数据到应用层 → 内存计算(危险!)
优化后(XML 聚合):在数据库层完成聚合 → 只返回计算结果(安全高效)

这时候要在数据库层面进行处理了;

// 新增 DAO 方法
@Select("SELECT area_id, " +"COUNT(*) AS flow, " +"AVG(car_speed) AS mean_speed, " +"SUM(CASE car_type WHEN '1' THEN 1 ELSE 0 END) AS small_CCCAR_flow, " +"SUM(CASE car_type WHEN '2' THEN 1 ELSE 0 END) AS medium_large_BBBULL_flow " +// 其他车型..."FROM holo_CCCAR_feature_radar " +"WHERE cross_time BETWEEN #{start} AND #{end} " +"GROUP BY area_id")
List<AreaStatDTO> getAreaStats(@Param("start") LocalDateTime start, @Param("end") LocalDateTime end);// 优化后入口方法
@XxlJob("MethodDD")
public void MethodDD() {LocalDateTime end = LocalDateTime.now().truncatedTo(ChronoUnit.DAYS);LocalDateTime start = end.minusDays(1);// 1. 数据库聚合计算List<AreaStatDTO> stats = CCCARRecordDAO.getAreaStats(start, end);// 2. 构建统计对象List<bbbPO> statsList = buildStatistics(stats, start);// 3. 批量存储statsService.saveBatch(statsList);// 4. 区域级统计saveAreaStatisticsDaily(statsList, start);
}

优化效果
数据量减少:假设原始数据10万条 → 聚合后100条区域数据

执行时间:从1200ms → 200ms

内存消耗:从800MB → 10MB

二、批量处理优化

  1. 批量插入代替逐条插入
// 原代码(逐条插入)
areaMap.forEach((areaId, areaList) -> {// ...构建postatsService.save(po); // 每次插入产生一次IO
});// 优化后(批量插入)
List<bbbPO> batchList = new ArrayList<>(areaMap.size());
areaMap.forEach((areaId, areaList) -> {// ...构建pobatchList.add(po);
});
statsService.saveBatch(batchList); // 一次批量插入
  1. 消除冗余流操作
// 原代码(两次遍历)
Map<String, List<AAA>> areaMap = list.stream().collect(groupingBy(...));
areaMap.forEach(...);// 优化后(合并处理)
list.stream().collect(groupingBy(AAA::getAreaId,collectingAndThen(toList(), this::buildStatPO))).values().forEach(...);

四、区域特殊处理解耦

  1. 定义区域配置策略
public enum SpecialArea {TUNNEL_1669("1669", "rightOfCrossTunnel"),TUNNEL_1670("1670", "leftOfCrossTunnel");private final String areaId;private final String fieldName;// 静态映射表private static final Map<String, SpecialArea> ID_MAP = Arrays.stream(values()).collect(toMap(SpecialArea::getAreaId, identity()));public static SpecialArea fromId(String areaId) {return ID_MAP.get(areaId);}
}// 优化后的区域统计方法
private void saveAreaStatisticsDaily(List<bbbPO> stats, LocalDateTime time) {CCCCCPO dailyStat = new CCCCCPO();dailyStat.setCrossTime(time);stats.forEach(po -> {SpecialArea area = SpecialArea.fromId(po.getAreaId());if (area != null) {BeanUtils.setProperty(dailyStat, area.getFieldName(), po.getFlow());}});dailyStat.setFlow(stats.stream().mapToInt(bbbPO::getFlow).sum());SERVICE1.save(dailyStat);
}

五、防御性编程增强

  1. 空值安全处理
// 平均速度计算优化
BigDecimal meanSpeed = areaList.stream().map(AAA::getCarSpeed).filter(Objects::nonNull).collect(Collectors.collectingAndThen(Collectors.averagingDouble(Double::doubleValue),avg -> avg.isNaN() ? BigDecimal.ZERO : BigDecimal.valueOf(avg)));
  1. 并行流安全控制
// 明确指定自定义线程池
ForkJoinPool customPool = new ForkJoinPool(4);
try {customPool.submit(() -> areaList.parallelStream()// ...处理逻辑).get();
} finally {customPool.shutdown();
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++设计模式】第四篇:建造者模式(Builder)

注意&#xff1a;复现代码时&#xff0c;确保 VS2022 使用 C17/20 标准以支持现代特性。 分步骤构造复杂对象&#xff0c;实现灵活装配 1. 模式定义与用途 核心目标&#xff1a;将复杂对象的构建过程分离&#xff0c;使得同样的构建步骤可以创建不同的表示形式。 常见场景&am…

uploadlabs经验总结

目录 一、基础上传漏洞&#xff08;太过简单目前环境不可能存在&#xff09; 1、抓包然后改后缀进行绕过 2、抓包然后改上传文件类型进行绕过 3、改后缀大小写绕过&#xff0c;以及收尾加空格&#xff0c;加::$DATA,加点等等 4、黑名单不完整绕过&#xff0c;复习后缀绕过&…

若依ry-vue分离板(完整版)前后端部署

目录 1.目标 2.准备工作 3.源码下载 4.整理前后端目录 5.先部署后端 &#xff08;1&#xff09;导入数据库 &#xff08;2&#xff09;改代码数据库配置 &#xff08;3&#xff09;运行redis &#xff08;4&#xff09;运行执行文件 &#xff08;5&#xff09;后端启…

重构谷粒商城09:人人开源框架的快速入门

谷粒商城09——人人开源框架的快速入门 前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶…

Linux | Vim 鼠标不能右键粘贴、跨系统复制粘贴

注&#xff1a;本文为 “ Vim 中鼠标右键粘贴、跨系统复制粘贴问题解决方案” 相关文章合辑。 未整理去重。 Linux 入门&#xff1a;vim 鼠标不能右键粘贴、跨系统复制粘贴 foryouslgme 发布时间 2016 - 09 - 28 10:24:16 Vim基础 命令模式(command-mode)插入模式(insert-m…

【JavaWeb】Web基础概念

文章目录 1、服务器与客户端2、服务器端应用程序3、请求和响应4、项目的逻辑构成5、架构5.1 概念5.2 发展演变历程单一架构分布式架构 5.3 单一架构技术体系 6、本阶段技术体系 1、服务器与客户端 ①线下的服务器与客户端 ②线上的服务器与客户端 2、服务器端应用程序 我…

基于云的内容中台核心优势是什么?

弹性云架构赋能资源整合 现代企业通过弹性云架构实现多源数据资源的深度整合&#xff0c;其动态扩展能力可自动适配业务流量波动。基于分布式存储与容器化部署&#xff0c;系统能够无缝对接CRM、ERP等企业软件集成&#xff0c;实现跨平台数据实时同步。值得注意的是&#xff0…

数据库基础练习1

目录 1.创建数据库和表 2.插入数据 创建一个数据库&#xff0c;在数据库种创建一张叫heros的表&#xff0c;在表中插入几个四大名著的角色&#xff1a; 1.创建数据库和表 #创建表 CREATE DATABASE db_test;#查看创建的数据库 show databases; #使用db_test数据库 USE db_te…

亲测解决笔记本触摸板使用不了Touchpad not working

这个问题可以通过FnFxx来解决&#xff0c;笔记本键盘上Fxx会有一个触摸板图标。如果不行应该玉藻设置中关了&#xff0c;打开即可。 解决办法 在蓝牙&#xff0c;触摸板里打开即可。 Turn it on in settings。

Vue23Web 基礎性拉滿的面試題(2025版)還沒更新完...

Vue2&3 基礎性1. 關於Vue2和Vue3生命週期的差別2. Vue2&3組件之間傳參不同點Vue2 傳遞與接收Vue3 傳遞與接收 (使用script setup語法糖)Vue3 傳遞與接收 (不使用script setup語法糖) 3. Vue2&3 keep-alive 組件Vue2 keep-aliveVue3 keep-alive 進階性爲什麽POST請求…

动态ip和静态ip适用于哪个场景?有何区别

在数字化浪潮席卷全球的今天&#xff0c;IP地址作为网络世界的“门牌号”&#xff0c;其重要性不言而喻。然而&#xff0c;面对动态IP与静态IP这两种截然不同的IP分配方式&#xff0c;许多用户往往感到困惑&#xff1a;它们究竟有何区别&#xff1f;又分别适用于哪些场景呢&…

深度学习模型Transformer核心组件—自注意力机制

第一章&#xff1a;人工智能之不同数据类型及其特点梳理 第二章&#xff1a;自然语言处理(NLP)&#xff1a;文本向量化从文字到数字的原理 第三章&#xff1a;循环神经网络RNN&#xff1a;理解 RNN的工作机制与应用场景(附代码) 第四章&#xff1a;循环神经网络RNN、LSTM以及GR…

FreeRTOS 源码结构解析与 STM32 HAL 库移植实践(任务创建、删除篇)

1. FreeRTOS源码结构介绍 1.1 下载源码 ​ 点击官网地址&#xff0c;选择 FreeRTOS 202212.01非 LTS 版本&#xff08;非长期支持版&#xff09;&#xff0c;因为这个版本有着最全的历程和更多型号处理器支持。 1.2 文件夹结构介绍 ​ 下载后主文件 FreeRTOSv202212.01 下包…

【uniapp】图片添加canvas水印

目录 需求&背景实现地理位置添加水印 ios补充 需求&背景 需求&#xff1a;拍照后给图片添加水印, 水印包含经纬度、用户信息、公司logo等信息。 效果图&#xff1a; 方案&#xff1a;使用canvas添加水印。 具体实现&#xff1a;上传图片组件是项目里现有的&#xff…

基于Windows11的DockerDesktop安装和布署方法简介

基于Windows11的DockerDesktop安装和布署方法简介 一、下载安装Docker docker 下载地址 https://www.docker.com/ Download Docker Desktop 选择Download for Winodws AMD64下载Docker Desktop Installer.exe 双点击 Docker Desktop Installer.exe 进行安装 测试Docker安装是…

AI自习室渐兴:人工智能赋能教育新场景的深度剖析

在数字化浪潮席卷全球的今天&#xff0c;教育领域也迎来了前所未有的变革。近年来&#xff0c;AI自习室作为人工智能技术与传统教育融合的产物&#xff0c;在河北等多地悄然兴起&#xff0c;成为学生们的新宠。这一新兴的学习场所&#xff0c;不仅引发了社会的广泛关注&#xf…

Android中AIDL和HIDL的区别

在Android中&#xff0c;AIDL&#xff08;Android Interface Definition Language&#xff09; 和 HIDL&#xff08;HAL Interface Definition Language&#xff09; 是两种用于定义跨进程通信接口的语言。AIDL 是 Android 系统最早支持的 IPC&#xff08;进程间通信&#xff0…

从0开始的操作系统手搓教程23:构建输入子系统——实现键盘驱动1——热身驱动

目录 所以&#xff0c;键盘是如何工作的 说一说我们的8042 输出缓冲区寄存器 状态寄存器 控制寄存器 动手&#xff01; 注册中断 简单整个键盘驱动 Reference ScanCode Table 我们下一步就是准备进一步完善我们系统的交互性。基于这个&#xff0c;我们想到的第一个可以…

【JavaEE】-- 多线程(初阶)4

文章目录 8.多线程案例8.1 单例模式8.1.1 饿汉模式8.1.2 懒汉模式 8.2 阻塞队列8.2.1 什么是阻塞队列8.2.2 生产者消费者模型8.2.3 标准库中的阻塞队列8.2.4 阻塞队列的应用场景8.2.4.1 消息队列 8.2.5 异步操作8.2.5 自定义实现阻塞队列8.2.6 阻塞队列--生产者消费者模型 8.3 …

用Python分割并高效处理PDF大文件

在处理大型PDF文件时&#xff0c;将它们分解成更小、更易于管理的块通常是有益的。这个过程称为分区&#xff0c;它可以提高处理效率&#xff0c;并使分析或操作文档变得更容易。在本文中&#xff0c;我们将讨论如何使用Python和为Unstructured.io库将PDF文件划分为更小的部分。…