如何将JPQL分组查询的Object数组结果映射至DTO?
解决JPQL聚合查询结果映射到DTO的问题
这问题我之前也碰到过,你现在的核心问题是:JPQL查询返回的是GameCatalog和Long组成的对象数组,但方法却声明返回List<Foo>,类型完全不匹配,而且你需要把结果映射到一个结构化的DTO里。下面是几种常用的解决方案:
方案1:构造函数投影(最直观的方式)
首先创建一个专门的DTO类,用来承载查询结果,注意要提供与JPQL查询字段顺序完全一致的构造函数:
// 替换成你的实际包路径 package com.yourproject.dto; public class GamePlayDurationDTO { private GameCatalog game; private Long duration; // 构造函数参数顺序必须和JPQL里select的字段顺序对应 public GamePlayDurationDTO(GameCatalog game, Long duration) { this.game = game; this.duration = duration; } // 按需添加getter方法,方便后续获取数据 public GameCatalog getGame() { return game; } public Long getDuration() { return duration; } }
然后修改你的Repository方法,在JPQL的select语句里使用new关键字调用DTO的全参构造函数,同时修改返回类型为List<GamePlayDurationDTO>:
@Repository public interface FooRepository extends JpaRepository<Foo, Id> { // 注意这里要写DTO的全类名,不能省略包路径 @Query("select new com.yourproject.dto.GamePlayDurationDTO(f.game, sum(f.timeSpent) as duration) from Foo f group by f.game order by duration desc") List<GamePlayDurationDTO> findMostPlayable(); }
方案2:接口投影(适合只读场景,无需编写DTO类)
如果你的场景只是读取数据,不需要修改DTO的内容,可以用接口投影的方式,Spring会动态生成代理类实现这个接口:
public interface GamePlayDurationProjection { // 方法名要和JPQL里的字段别名对应(getXxx对应别名xxx) GameCatalog getGame(); Long getDuration(); }
然后修改Repository方法,给JPQL里的字段加上别名(别名要和接口的getter方法去掉get后的名称一致),返回类型改为List<GamePlayDurationProjection>:
@Repository public interface FooRepository extends JpaRepository<Foo, Id> { @Query("select f.game as game, sum(f.timeSpent) as duration from Foo f group by f.game order by duration desc") List<GamePlayDurationProjection> findMostPlayable(); }
注意事项
- 原来的方法返回
List<Foo>是错误的,因为你的查询结果并不是Foo实体对象,而是两个字段的聚合结果,必须修改返回类型为对应的DTO或投影接口。 - 用构造函数投影时,一定要保证JPQL的字段顺序和DTO构造函数的参数顺序完全一致,否则会出现类型不匹配的错误。
- 如果是复杂的映射场景(比如关联多个实体的复杂结果),还可以用
@SqlResultSetMapping配合@ConstructorResult来实现,但前面两种方案已经能覆盖大部分常见场景了。
内容的提问来源于stack exchange,提问作者Viktor M.
相关产品推荐
相关产品推荐

