如何将JPA Repository查询结果转换为自定义StatisticDto类型
问题背景
现有可正常执行的JPA Repository查询代码如下:
public interface GameRepository extends JpaRepository<Game,Integer> { @Query(value="SELECT Cast(bifnb as varchar) bifnb , count(*) FROM (SELECT count(fk_game) as nb FROM public.game INNER JOIN score s on game.id_game = s.fk_game WHERE fk_board_game = 2014 GROUP BY fk_game) as bifnb group by bifnb", nativeQuery = true) List<StatisticDto> nbplayer(); }
使用StatisticDto接收原生查询结果时抛出如下异常:
org.springframework.core.convert.ConverterNotFoundException: 找不到可将类型[org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap]转换为类型[****.statistics.payload.StatisticDto]的转换器
将方法返回值改为List<Object>后查询可正常执行,但期望直接使用StatisticDto类型接收结果。
报错原因
使用nativeQuery = true标记的原生SQL查询,Spring Data JPA默认会将结果封装为TupleBackedMap结构,不会自动完成到自定义POJO类的类型转换,必须显式配置映射规则才能直接返回自定义DTO类型。
可行解决方案
方案1:接口投影(改动最小,无需额外映射配置)
这是原生查询场景下最简便的实现方式,不需要修改原有查询逻辑,只需定义和返回字段匹配的投影接口即可:
- 定义投影接口,方法名和查询字段别名严格对应:
public interface StatisticProjection { // 对应SQL里的bifnb字段 String getBifnb(); // 对应count(*)的返回值,注意count聚合结果为Long类型,不要用Integer接收 Long getTotal(); } - 修改原SQL,给无别名的
count(*)加上和投影接口匹配的别名,调整Repository返回值类型:public interface GameRepository extends JpaRepository<Game,Integer> { @Query(value="SELECT Cast(bifnb as varchar) bifnb , count(*) as total FROM (SELECT count(fk_game) as nb FROM public.game INNER JOIN score s on game.id_game = s.fk_game WHERE fk_board_game = 2014 GROUP BY fk_game) as bifnb group by bifnb", nativeQuery = true) List<StatisticProjection> nbplayer(); }
使用时直接调用接口的getBifnb()、getTotal()方法即可获取值,使用体验和普通DTO无差异。
方案2:构造函数结果映射(适合必须使用具体DTO类的场景)
如果一定要返回StatisticDto具体类而非接口,可通过JPA的@SqlResultSetMapping显式定义结果和DTO的映射关系:
- 给
StatisticDto添加字段顺序、类型完全匹配的全参构造函数,补充对应getter/setter:public class StatisticDto { private String bifnb; private Long total; // 必须保留无参构造(部分JPA实现需要) public StatisticDto() {} // 和查询返回字段顺序、类型一一对应的全参构造 public StatisticDto(String bifnb, Long total) { this.bifnb = bifnb; this.total = total; } // 省略getter、setter } - 在
Game实体类上配置命名原生查询和结果集映射:@Entity @NamedNativeQuery( name = "Game.nbplayer", query = "SELECT Cast(bifnb as varchar) bifnb , count(*) as total FROM (SELECT count(fk_game) as nb FROM public.game INNER JOIN score s on game.id_game = s.fk_game WHERE fk_board_game = 2014 GROUP BY fk_game) as bifnb group by bifnb", resultSetMapping = "statisticDtoMap" ) @SqlResultSetMapping( name = "statisticDtoMap", classes = @ConstructorResult( targetClass = StatisticDto.class, columns = { @ColumnResult(name = "bifnb", type = String.class), @ColumnResult(name = "total", type = Long.class) } ) ) public class Game { // 实体原有字段、方法不变 } - 移除Repository方法上的
@Query注解,方法名和命名查询的name对应即可:public interface GameRepository extends JpaRepository<Game,Integer> { List<StatisticDto> nbplayer(); }
方案3:高版本JPA直接映射(Spring Boot 2.6+可用)
如果项目使用的Spring Data JPA版本在2.6及以上,只需保证:
StatisticDto有对应字段的getter/setter、无参构造- 原生SQL的所有返回字段都有和
StatisticDto属性名完全一致的别名 - 字段类型严格匹配(count结果用Long接收,不要用Integer)
即可直接返回List<StatisticDto>,不需要额外配置。
避坑提示
- 数据库
count(*)聚合函数返回的是Long/BigInteger类型,不要用Integer类型接收,否则会抛出类型转换异常 - 原生查询的所有返回字段必须显式指定别名,且别名要和投影/DTO的属性名严格匹配,否则会出现字段值为null的问题
内容的提问来源于stack exchange,提问作者Orel M
相关产品推荐
相关产品推荐

