问题现象

教学数据仪表盘中的「知识点掌握度热力图」区域空白,不显示任何数据。浏览器控制台显示接口返回 500 错误。

热力图空白示意图

排查过程

第一步:确认数据链路

首先检查后端的 HomeworkKnowledgeMapper,发现 SQL 查询逻辑是正确的:

  • homework_knowledge 表查询知识点掌握记录
  • JOIN homework_evaluation 表按班级筛选
  • 按知识点分组计算平均掌握度

数据流设计没有问题,直接查询批改结果表,避免了知识点名称映射的混乱。

第二步:定位后端异常

查看后端日志,发现具体的错误信息:

1
java.lang.ClassCastException: class java.math.BigDecimal cannot be cast to class java.lang.Double

错误发生在 DashboardServiceImpl.java 第 121 行。

第三步:分析根因

问题出在类型转换上:

1
2
// 原代码
Double avgMastery = (Double) stat.get("avg_mastery");

PostgreSQL 的 AVG() 函数返回的是 BigDecimal 类型,而不是 Double。直接使用强制类型转换会抛出 ClassCastException

这是不同数据库之间的差异:

数据库 AVG() 返回类型
MySQL Double / Long
PostgreSQL BigDecimal
Oracle BigDecimal

解决方案

修改 DashboardServiceImpl.java 中的类型处理逻辑:

1
2
3
4
5
6
7
8
9
// 修复后的代码
Object avgMasteryObj = stat.get("avg_mastery");
int mastery = 0;
if (avgMasteryObj != null) {
if (avgMasteryObj instanceof Number) {
mastery = ((Number) avgMasteryObj).intValue();
}
}
dto.setMastery(mastery);

使用 Number 接口作为中间层,可以兼容 BigDecimalDoubleFloat 等各种数值类型。

经验总结

1. 数据库兼容性注意点

当使用聚合函数(AVG()SUM()COUNT() 等)时,不同数据库的返回类型可能不同。编写跨数据库兼容的代码时,需要特别注意这一点。

2. 防御性编程

处理数据库查询结果时,避免直接强制类型转换:

1
2
3
4
5
6
// ❌ 不推荐 - 可能抛出 ClassCastException
double value = (Double) map.get("column");

// ✅ 推荐 - 安全类型转换
Object obj = map.get("column");
double value = obj instanceof Number ? ((Number) obj).doubleValue() : 0.0;

3. 日志的重要性

如果没有后端详细的异常堆栈,这个问题很难定位。生产环境务必开启完整的日志记录,包括:

  • SQL 执行日志
  • 异常堆栈信息
  • 请求/响应参数

最终效果

修复后热力图正常显示,按掌握度从低到高排序,颜色区分:

  • 🔴 红色:掌握度 < 60%(薄弱)
  • 🟡 黄色:掌握度 60%-80%(一般)
  • 🟢 绿色:掌握度 > 80%(良好)

技术栈

  • 后端: Spring Boot + MyBatis-Plus + PostgreSQL
  • 前端: Vue 3
  • 问题类型: 数据库类型兼容性问题
  • 修复耗时: 10 分钟

本文首发于个人博客,转载请注明出处。