关键词:MyBatis、ClassCastException、COUNT 聚合、类型映射、Spring Boot
一、问题的出现
在一个 Spring Boot + MyBatis 的报表统计接口中,调用ReportServiceIml.getEmpJobData()时,控制台抛出了如下异常:
java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.Integer at com.mlamp.service.impl.ReportServiceIml.lambda$getEmpJobData$1(ReportServiceIml.java:24) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)报错位置指向第 24 行:
jobOption.setDataList(list.stream().map(m->(Integer)m.get("count")).toList());异常堆栈很清晰地告诉我们:Long类型的对象无法被强转为Integer。
二、根因分析
1. MyBatis 对聚合函数结果类型的默认映射
我们的数据来自一条COUNT()聚合查询:
List<Map<String,Object>>list=empMapper.getJobList();当查询结果以Map<String, Object>形式返回时,MyBatis 并不会把数据库字段"智能"地映射成你期望的类型,而是按照 JDBC 的默认规则转换:
| SQL 类型 | 示例 | MyBatis / JDBC 默认映射的 Java 类型 |
|---|---|---|
BIGINT | 自增主键、COUNT 结果 | Long |
INT/INTEGER | 普通整型字段 | Integer |
DECIMAL | SUM 金额等 | BigDecimal |
注意:COUNT(*)、COUNT(1)这类聚合函数的返回类型在 JDBC 规范中是BIGINT,因此 MyBatis 会把它映射为Long,而不是Integer。
2. 为什么强转会失败?
Long和Integer都是Number的子类,彼此之间没有继承关系。Java 的强制类型转换((Integer) obj)是针对引用类型的,只有在对象的实际运行时类型与目标类型一致(或存在继承关系)时才能成功。把一个实际是Long的对象强转成Integer,JVM 在运行时就会抛出ClassCastException。
这与"自动拆箱 + 数值转换"是两回事。
(int) longValue这种基本类型转换是合法的,但(Integer) (Long 对象)这种引用类型转换是不合法的。
3. 出错代码
// 字段实际上是一个 Long 对象jobOption.setDataList(list.stream().map(m->(Integer)m.get("count")).toList());m.get("count")返回的是Long,再(Integer)强转,于是炸了。
三、解决方案
目标:把Long安全、明确地转换为int,再塞进List<Integer>。
方案一(推荐):Math.toIntExact
jobOption.setDataList(list.stream().map(m->Math.toIntExact((Long)m.get("count"))).toList());Math.toIntExact(long)会把long转为int,并在数值超出int取值范围(-2^31 ~ 2^31-1)时主动抛出ArithmeticException。对统计数据量这种场景,这是一个安全的"早失败"保护。
方案二:直接调用intValue()
jobOption.setDataList(list.stream().map(m->((Long)m.get("count")).intValue()).toList());简单直接,但数值溢出时不会报错(直接截断高位),适合你能确定数据量不会爆int范围的场景。
方案三(治本):从 SQL/结果类型上规避
如果希望从根源上得到Integer,可以在 Mapper 中:
- 使用
@MapKey或定义专门的 DTO/实体类,配合<result>明确指定jdbcType与javaType; - 或在 SQL 中使用
CAST(COUNT(*) AS SIGNED)等(仍然建议接收端用Long,因为统计结果本就可能很大)。
一般来说,统计类结果用Long/BigDecimal接收是最稳妥的,上面的方案一/二足以应对绝大多数业务。
四、经验总结
COUNT()、SUM()等聚合函数的结果,在 MyBatis 中通常映射为Long(BigDecimal),而不是Integer。Map<String, Object>接收查询结果时,要清楚每个字段的实际类型,不要凭直觉强转。- 引用类型之间不能随意强转,
Long/Integer没有继承关系,必须用数值转换方法(如intValue()、Math.toIntExact())。 - 涉及可能大数值的统计量,优先使用
Long或BigDecimal承载,避免int溢出。
一个小小的统计接口,背后是对 JDBC 类型映射规则的理解偏差。把Long当Integer用,是 MyBatis 初学者最常见的坑之一,记住它,能省下不少排查时间。