优化实践
# 高性能驾驶舱聚合实践
- 背景:/api/report/v1/homeDash/stat/list 需要同时返回安检、工单、抄表三大板块的集团级指标。原实现直接 selectList 出 20万~80万行,再在 Java Stream 里 groupingBy, 单次请求要 90s+。
- 痛点:MyBatis 默认的 BaseMapper#selectList 只能按单条记录映射,导致海量数据跨进程搬运;复杂 SQL 写在注解里也难维护,更放大调优成本。
落地步骤
- 抽象聚合输出 三张统计表都最终映射为 HomeDashBizStatVo,所以直接在 Mapper 层暴露 aggregateForHomeDash(String statPeriodValue, String accessGrpCd, String deletedFlg),统一返回 VO,避免实体→VO 二次转换。
- SQL 下沉至 XML 把业务聚合写入 ncis-backend-report-service/src/main/resources/mybatis/yasdb/mapper/report/*.xml,示例:
<select id="aggregateForHomeDash" resultType="com.saiit.ncis.report.module.report.vo.HomeDashBizStatVo">
SELECT ACCESS_GRP_CD AS accessGrpCd,
MAX(CIS_DIVISION) AS cisDivision,
ROUND(
CASE WHEN COALESCE(SUM(INSPECTION_COUNT), 0) > 0
THEN COALESCE(SUM(COMPLETED_COUNT), 0) * 100.0 / COALESCE(SUM(INSPECTION_COUNT), 0)
ELSE 0 END, 2) AS inspectionRate,
COALESCE(SUM(INSPECTION_COUNT), 0) AS inspectionCount,
COALESCE(SUM(ABNORMAL_COUNT), 0) AS abnormalCount
FROM BI_SC_STAT
WHERE DELETED_FLG = #{deletedFlg}
AND STAT_PERIOD_VALUE = #{statPeriodValue}
<if test="accessGrpCd != null and accessGrpCd != ''">
AND ACCESS_GRP_CD = #{accessGrpCd}
</if>
GROUP BY ACCESS_GRP_CD
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 精简 WHERE:只保留一次 DELETED_FLG = ?,充分利用复合索引 (DELETED_FLG, STAT_PERIOD_VALUE, ACCESS_GRP_CD)。
- 聚合逻辑由数据库完成,返回行数≈访问组数量,天然压缩 IO。
- Service 统一接口 在 IBi*StatService 增加 aggregateForHomeDash,实现类注入 ACTIVE_FLAG = "U" 后转调 Mapper。后续如需加缓存/多租户过滤,可在 Service 层集中处理。
- Processor 仅做排序 Sc/Order/MeterHomeDashStatProcessor 直接消费服务返回值,只做排序即可,不再 groupingBy or sumInt。代码更薄,更符合“单方法 <30 行”的规范。
效果与收益
- 性能:接口耗时从 90s → 2s,主要得益于数据量级从 20 万+ 行降到几十行。
- 资源:CPU/内存占用显著下降,减少 GC 抖动;数据库只需做一次聚合扫描,配合索引走范围查询。
- 维护:复杂 SQL 统一放在 XML,参数检查、字段别名一目了然,DBA 可以直接复用 SQL 做 Explain 或加 hint。
最佳实践 Checklist
- 认清数据粒度:REST 返回的粒度若已是“访问组”,就不应该在应用层拿“明细户”再聚合。
- 优先让数据库干数据库的活:SUM/ROUND/GROUP BY 属于典型 OLAP 场景,交给 SQL 引擎天然更快。
- SQL 同步沉淀:复杂语句放 XML,结合
写动态条件;Java 保持接口签名即可。 - 守护索引策略:复合索引字段顺序要匹配 WHERE,必要时追加 GROUP BY 列提高覆盖率。
- 接口分层单一职责:Processor 只负责业务编排/排序,Service 负责数据准备,Mapper 负责 SQL ——层层清晰,后续扩展(并行查询 / 缓存)也更简单。
按这个套路,后续任何“驾驶舱类”接口都能快速复制:先确定最终粒度,再写一次聚合 SQL,下游组件天然受益。
完善页面 (opens new window)