当前位置: 首页 > article >正文

浅论数据库聚合:合理使用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();
}

在这里插入图片描述


http://www.kler.cn/a/577476.html

相关文章:

  • P4268 [USACO18FEB] Directory Traversal G
  • 使用Lua和lua-resty-http-simple库的爬虫程序爬取图片
  • linyu-im
  • 蓝桥杯备赛:一道数学题(练思维(同余的应用))
  • 解决MySQL迁移到达梦数据库报错“字符串截断”的问题
  • Android Studio右上角Gradle 的Task展示不全
  • 《原型链的故事:JavaScript 对象模型的秘密》
  • 【0013】Python数据类型-列表类型详解
  • R软件线性模型与lmer混合效应模型对生态学龙类智力测试数据层级结构应用
  • 力大砖飞,纯暴力搜索——蓝桥p2110(写着玩的)
  • 深入理解 Java 中的 CopyOnWrite 机制
  • 【Go每日一练】返回切片中的最大值和最小值
  • GIMP 2.10 开源图像编辑软件安装教程(Windows平台)
  • 2024 Qiniu 跨平台 Qt 高级开发全解析
  • RISC-V汇编学习(三)—— RV指令集
  • 网络编程-----服务器(多路复用IO 和 TCP并发模型)
  • 题解:洛谷 AT_dp_c Vacation
  • 网络HTTP
  • 加速科技Flex10K-L测试机:以硬核创新重塑显示驱动芯片测试新标杆!
  • C#UDP通讯(数据接收)