一次 MyBatis 统计查询引发的 ClassCastException:Long 不能强转为 Integer

关键词: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
DECIMALSUM 金额等BigDecimal

注意:COUNT(*)COUNT(1)这类聚合函数的返回类型在 JDBC 规范中是BIGINT,因此 MyBatis 会把它映射为Long,而不是Integer

2. 为什么强转会失败?

LongInteger都是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>明确指定jdbcTypejavaType
  • 或在 SQL 中使用CAST(COUNT(*) AS SIGNED)等(仍然建议接收端用Long,因为统计结果本就可能很大)。

一般来说,统计类结果用Long/BigDecimal接收是最稳妥的,上面的方案一/二足以应对绝大多数业务。

四、经验总结

  1. COUNT()SUM()等聚合函数的结果,在 MyBatis 中通常映射为LongBigDecimal),而不是Integer
  2. Map<String, Object>接收查询结果时,要清楚每个字段的实际类型,不要凭直觉强转。
  3. 引用类型之间不能随意强转Long/Integer没有继承关系,必须用数值转换方法(如intValue()Math.toIntExact())。
  4. 涉及可能大数值的统计量,优先使用LongBigDecimal承载,避免int溢出。

一个小小的统计接口,背后是对 JDBC 类型映射规则的理解偏差。把LongInteger用,是 MyBatis 初学者最常见的坑之一,记住它,能省下不少排查时间。