浅论数据库聚合:合理使用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) -> {
    // ...构建po
    statsService.save(po); // 每次插入产生一次IO
});

// 优化后(批量插入)
List<bbbPO> batchList = new ArrayList<>(areaMap.size());
areaMap.forEach((areaId, areaList) -> {
    // ...构建po
    batchList.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();
}

在这里插入图片描述

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

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

相关文章

DeepSeek大模型深度解析:架构、技术与应用全景

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。https://www.captainbed.cn/north 文章目录 一、大模型时代与DeepSeek的定位1.1 大模型发展历程回顾大模型发展历程时间轴&#xff08;20…

极狐GitLab 正式发布安全版本17.9.1、17.8.4、17.7.6

本分分享极狐GitLab 补丁版本 17.9.1、17.8.4、17.7.6 的详细内容。这几个版本包含重要的缺陷和安全修复代码&#xff0c;我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS&#xff0c;技术团队已经进行了升级&#xff0c;无需用户采取任何…

windows server 2019创建教程,新建存储池,新建虚拟磁盘,文件共享

1.网页地址Windows Server 2019 ISO镜像下载 - 我的MSDN 迅雷下载路径ed2k://|file|cn_windows_server_2019_updated_jan_2020_x64_dvd_4bbe2c37.iso|5608552448|39C663ABF26079240030395C7CB3F975|/ 开始创建 注意;虽然我们的镜像是windows server2019,但是虚拟机最高只能选w…

Linux系统之配置HAProxy负载均衡服务器

Linux系统之配置HAProxy负载均衡服务器 前言一、HAProxy介绍1.1 HAProxy简介1.2 主要特点1.3 使用场景二、本次实践介绍2.1 本次实践简介2.2 本次实践环境规划三、部署两台web服务器3.1 运行两个Docker容器3.2 编辑测试文件3.3 访问测试四、安装HAProxy4.1 更新系统软件源4.2 安…

使用Dockerfile打包java项目生成镜像部署到Linux_java项目打docker镜像的dockerfile

比起容器、镜像来说&#xff0c;Dockerfile 非常普通&#xff0c;它就是一个纯文本&#xff0c;里面记录了一系列的构建指令&#xff0c;比如选择基础镜像、拷贝文件、运行脚本等等&#xff0c;每个指令都会生成一个 Layer&#xff0c;而 Docker 顺序执行这个文件里的所有步骤&…

【TCP/IP协议栈】【传输层】端口号、套接字、多路复用/分解、网络字节序

参考资料&#xff1a; 前言&#xff1a; 总结&#xff1a; 【计算机网络】套接字&#xff08;应用层和传输层之间的接口&#xff09; 套接字是一个通用的通信接口抽象不仅限于TCP/IP协议族作为应用层和传输层之间的桥梁支持多种通信方式和协议族 套接字定义 在 TCP 或者 UDP…

【五.LangChain技术与应用】【31.LangChain ReAct Agent:反应式智能代理的实现】

一、ReAct Agent是啥?为什么说它比「普通AI」聪明? 想象一下,你让ChatGPT查快递物流,它可能直接编个假单号糊弄你。但换成ReAct Agent,它会先推理(Reasoning)需要调用哪个接口,再行动(Action)查询真实数据——这就是ReAct的核心:让AI学会「动脑子」再动手。 举个真…

软件信息安全性测试流程有哪些?专业软件测评服务机构分享

在数字化时代&#xff0c;软件信息安全性测试的重要性愈发凸显。尤其是对于企业来说&#xff0c;确保软件的安全性不仅是维护用户信任的关键&#xff0c;也是满足合规要求的必要条件。 软件信息安全性测试是指通过一系列系统化的测试手段&#xff0c;评估软件应用在受到攻击时…

SparkAi系统体验

DeepSeek-R1-671B大模型满血版私有化部署高可用教程-SparkAi系统集成图文教程 一、SparkAI是什么二、功能模块介绍系统快速体验 三、系统功能模块3.1 AI全模型支持/插件系统3.2 AI智能体应用3.3 AI专业绘画3.4 AI视频生成3.5 Dall-E2/E3/E4绘画3.6 智能思维导图生成3.7 AI绘画广…

Cursor如何调试.Net Core控制台程序

1.背景 在Cursor下调试.Net Core控制台程序会出现下面的问题&#xff1a; 因为Cursor是VS Code的变种版本&#xff0c;并不被官方的调试机制支持去使用。基于这种情况&#xff0c;就产生了本文。 2.解决方法 使用三星电子的开源调试工具netcoredbg&#xff0c;就能解决这个问…

记一次ScopeSentry搭建

介绍 Scope Sentry是一款具有资产测绘、子域名枚举、信息泄露检测、漏洞扫描、目录扫描、子域名接管、爬虫、页面监控功能的工具&#xff0c;通过构建多个节点&#xff0c;自由选择节点运行扫描任务。当出现新漏洞时可以快速排查关注资产是否存在相关组件。 目前功能 插件系…

Aws batch task 无法拉取ECR 镜像unable to pull secrets or registry auth 问题排查

AWS batch task使用了自定义镜像&#xff0c;在提作业后出现错误 具体错误是ResourceInitializationError: unable to pull secrets or registry auth: The task cannot pull registry auth from Amazon ECR: There is a connection issue between the task and Amazon ECR. C…

GPT 4.5 可能是戳破 AI 泡沫的模型

GPT 4.5 可能是戳破 AI 泡沫的模型 Andrew Zuo 本文点评&#xff1a;在AI技术狂飙突进的同时&#xff0c;也有许多声音包括本文的作者在内都认为AI行业正陷入巨大泡沫&#xff0c;技术突破逐渐停滞&#xff0c;高昂的硬件成本与资本退潮或将引爆寒冬。然而&#xff0c;这些观点…

【Linux内核系列】:进入文件系统的世界

&#x1f525; 本文专栏&#xff1a;Linux &#x1f338;作者主页&#xff1a;努力努力再努力wz 那么从本篇文章开始就要进入文件系统的学习了&#xff0c;那么之前的内容主要围绕的是进程的相关概念以及进程控制有关的系统调用接口的介绍&#xff0c;以及最后结合之前所学的知…

CentOS 7.9 安装 ClickHouse 文档

1. 环境准备 确保系统为 CentOS 7.9&#xff0c;并已安装 Docker。如果未安装 Docker&#xff0c;请先安装 Docker。 安装 Docker # 卸载旧版本 Docker&#xff08;如果有&#xff09; sudo yum remove -y docker docker-client docker-client-latest docker-common docker-…

(链表 删除链表的倒数第N个结点)leetcode 19

设空结点指向head便于插入和删除结点 考虑特殊情况 head结点被删除 a结点仅用来测试长度&#xff0c;找到目标结点的位置 b结点为空结点指向head返回值 cur用来删除目标值&#xff08;特殊情况 目标值为head 这时curb) 则开始就将cur初始化为b开始遍历 /*** Definition fo…

电力杆塔倾斜监测装置:守护电网安全的智能卫士

​ ​电力杆塔作为电力传输的重要支撑结构&#xff0c;其安全性直接关系到电网的稳定运行和电力供应的可靠性。然而&#xff0c;由于自然环境的复杂性和外部因素的影响&#xff0c;杆塔倾斜、倒塌等问题时有发生&#xff0c;给电力系统带来了巨大的安全隐患。为了应对这一挑…

【单片机项目】电源如何扩展、电源模块、电池模块如何接线

一、前言 解决2个关键问题&#xff1a; 【1】如果项目编号小于172之前的项目。 可能会遇到电源模块不够接&#xff0c;需要扩展电源的问题。 【2】如果项目编号是大于 172之后项目&#xff0c;部分项目用到了稳压电源模块或者是电池模块。 这篇文章单独讲解一下如何接线。 …

NO.28十六届蓝桥杯备战|string|insert|find|substr|关系运算|stoi|stol|stod|stof|to_string(C++)

insert 如果我们需要在字符串中间的某个位置插⼊⼀个字符串&#xff0c;得掌握⼀个函数就是insert string& insert (size_t pos, const string& str); //pos位置前⾯插⼊⼀个string字符串 string& insert (size_t pos, const char* s); //pos位置前⾯插⼊⼀个…

贪心算法一

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解什么是贪心算法&#xff0c;并且掌握贪心算法。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! >…