Spring CRUD Repository为何count返回long,更新/删除仅支持int?
为什么@Modifying查询不能返回Long?
这是因为JPA规范中Query.executeUpdate()方法的返回类型就是int,Spring Data严格遵循这个规范,所以被@Modifying注解标记的修改类查询(delete/update)只能使用void、int或Integer作为返回类型,这就是你看到报错的直接原因。
早期JPA设计时,认为大部分场景下修改操作的受影响行数不会超过int的最大值(约21亿),所以采用int作为返回类型;而count()方法返回Long是因为统计全量数据时,很容易突破int的范围,Spring Data特意做了适配来避免溢出问题——这是两种不同场景的差异化设计,并非前后不一致。
解决方法
如果你的业务场景确实需要获取超过int范围的受影响行数,可以尝试以下方案:
方案1:分两步操作(非原子)
先执行count查询获取符合条件的记录数(返回Long),再执行删除操作。注意这两步不是原子操作,中间如果有其他线程修改数据,count结果和实际删除行数会有差异,需要结合业务场景判断是否可接受:
// 先统计符合条件的记录数 @Query(nativeQuery = true, value = "select count(*) from AUDIT_CREATE_SAVINGS_EASY_ACCESS_ACCOUNT where timestamp < :cutOff") fun countByTimestampBefore(@Param("cutOff") cutOff: Instant): Long // 再执行删除操作 @Modifying @Query(nativeQuery = true, value = "delete from AUDIT_CREATE_SAVINGS_EASY_ACCESS_ACCOUNT where timestamp < :cutOff") fun deleteByTimestampBefore(@Param("cutOff") cutOff: Instant): Int
方案2:使用原生JDBC获取精确原子行数
通过JdbcTemplate执行删除,并利用数据库的内置函数获取受影响行数(比如MySQL的ROW_COUNT()),这样可以拿到Long类型的结果,且整个操作是原子的:
@Autowired private lateinit var jdbcTemplate: JdbcTemplate fun deleteAndGetRowCount(cutOff: Instant): Long { // 执行删除语句 jdbcTemplate.update("delete from AUDIT_CREATE_SAVINGS_EASY_ACCESS_ACCOUNT where timestamp < ?", cutOff) // 查询本次操作的受影响行数 return jdbcTemplate.queryForObject("SELECT ROW_COUNT()", Long::class.java) ?: 0 }
注意不同数据库获取受影响行数的语法不同:比如PostgreSQL需要用GET DIAGNOSTICS row_count = ROW_COUNT,需要根据你的数据库类型调整。
方案3:升级Spring Data版本(适配大数值返回)
从Spring Data JPA 2.4版本开始,官方对@Modifying查询的返回类型做了扩展,支持Long/long作为返回类型(底层通过适配不同数据库驱动来处理超大数值场景)。如果你的项目版本较低,可以尝试升级到2.4+版本,这样你原来定义的方法就可以直接正常使用了。
内容的提问来源于stack exchange,提问作者Mr. Tomato

