如何从Spring Data JPA GROUP BY查询返回自定义对象

Pra*_*lan 85 java spring jpa spring-mvc repository

我正在使用Spring Data JPA开发Spring Boot应用程序.我正在使用自定义JPQL查询按某些字段进行分组并获取计数.以下是我的存储库方法.

@Query(value = "select count(v) as cnt, v.answer from Survey v group by v.answer")
public List<?> findSurveyCount();
Run Code Online (Sandbox Code Playgroud)

它的工作和结果如下:

[
  [1, "a1"],
  [2, "a2"]
]
Run Code Online (Sandbox Code Playgroud)

我想得到这样的东西:

[
  { "cnt":1, "answer":"a1" },
  { "cnt":2, "answer":"a2" }
]
Run Code Online (Sandbox Code Playgroud)

我怎样才能做到这一点?

man*_*ish 200

JPQL查询的解决方案

JPA规范中的 JPQL查询支持此功能.

第1步:声明一个简单的bean类

package com.path.to;

public class SurveyAnswerStatistics {
  private String answer;
  private Long   cnt;

  public SurveyAnswerStatistics(String answer, Long cnt) {
    this.answer = answer;
    this.count  = cnt;
  }
}
Run Code Online (Sandbox Code Playgroud)

第2步:从存储库方法返回bean实例

public interface SurveyRepository extends CrudRepository<Survey, Long> {
    @Query("SELECT " +
           "    new com.path.to.SurveyAnswerStatistics(v.answer, COUNT(v)) " +
           "FROM " +
           "    Survey v " +
           "GROUP BY " +
           "    v.answer")
    List<SurveyAnswerStatistics> findSurveyCount();
}
Run Code Online (Sandbox Code Playgroud)

重要笔记

  1. 确保提供bean类的完全限定路径,包括包名称.例如,如果调用bean类MyBean并且它在包中com.path.to,那么bean的完全限定路径将是com.path.to.MyBean.简单地提供MyBean将不起作用(除非bean类在默认包中).
  2. 确保使用new关键字调用bean类构造函数.SELECT new com.path.to.MyBean(...)会工作,而SELECT com.path.to.MyBean(...)不会.
  3. 确保以与bean构造函数中预期的顺序完全相同的顺序传递属性.尝试以不同的顺序传递属性将导致异常.
  4. 确保查询是有效的JPA查询,也就是说,它不是本机查询.@Query("SELECT ..."),或@Query(value = "SELECT ..."),或@Query(value = "SELECT ...", nativeQuery = false)将工作,而@Query(value = "SELECT ...", nativeQuery = true)不会工作.这是因为本机查询在没有修改的情况下传递给JPA提供程序,因此会针对底层RDBMS执行.由于new并且com.path.to.MyBean不是有效的SQL关键字,因此RDBMS会抛出异常.

原生查询的解决方案

如上所述,new ...语法是JPA支持的机制,适用于所有JPA提供程序.但是,如果查询本身是不是一个JPA查询,也就是说,它是一种天然的查询时,new ...作为查询被传递直接到底层RDBMS,不明白的语法是行不通的new,因为它不是一部分的关键字SQL标准.

在这种情况下,需要使用Spring Data Projection接口替换bean类.

第1步:声明投影界面

package com.path.to;

public interface SurveyAnswerStatistics {
  String getAnswer();

  int getCnt();
}
Run Code Online (Sandbox Code Playgroud)

第2步:从查询中返回投影属性

public interface SurveyRepository extends CrudRepository<Survey, Long> {
    @Query(nativeQuery = true, value =
           "SELECT " +
           "    v.answer AS answer, COUNT(v) AS cnt " +
           "FROM " +
           "    Survey v " +
           "GROUP BY " +
           "    v.answer")
    List<SurveyAnswerStatistics> findSurveyCount();
}
Run Code Online (Sandbox Code Playgroud)

使用SQL AS关键字将结果字段映射到投影属性以进行明确映射.

  • bean类必须是完全限定的,即包含完整的包名.像`com.domain.dto.SurveyAnswerReport`这样的东西. (8认同)
  • 当我尝试从我的“JpaRepository”返回自定义类型时,我收到“java.lang.IllegalArgumentException: PersistentEntity must not be null!”?我错过了一些配置吗? (3认同)

ozg*_*gur 17

这个SQL查询返回List <Object []>会.

你可以这样做:

 @RestController
 @RequestMapping("/survey")
 public class SurveyController {

   @Autowired
   private SurveyRepository surveyRepository;

     @RequestMapping(value = "/find", method =  RequestMethod.GET)
     public Map<Long,String> findSurvey(){
       List<Object[]> result = surveyRepository.findSurveyCount();
       Map<Long,String> map = null;
       if(result != null && !result.isEmpty()){
          map = new HashMap<Long,String>();
          for (Object[] object : result) {
            map.put(((Long)object[0]),object[1]);
          }
       }
     return map;
     }
 }
Run Code Online (Sandbox Code Playgroud)


ren*_*ena 12

我知道这是一个古老的问题,它已经得到了解答,但这是另一种方法:

@Query("select new map(count(v) as cnt, v.answer) from Survey v group by v.answer")
public List<?> findSurveyCount();
Run Code Online (Sandbox Code Playgroud)


Nic*_*nia 8

使用接口,您可以获得更简单的代码。无需创建和手动调用构造函数

第1步:使用必填字段声明界面:

public interface SurveyAnswerStatistics {

  String getAnswer();
  Long getCnt();

}
Run Code Online (Sandbox Code Playgroud)

步骤2:在接口中选择与getter名称相同的列,并从存储库方法返回intefrace:

public interface SurveyRepository extends CrudRepository<Survey, Long> {

    @Query("select v.answer as answer, count(v) as cnt " +
           "from Survey v " +
           "group by v.answer")
    List<SurveyAnswerStatistics> findSurveyCount();

}
Run Code Online (Sandbox Code Playgroud)

  • @geneb。无论如何,你不应该在 GUI 层中使用这些对象。这意味着您将数据层与用户界面层紧密耦合,这是一种糟糕的架构:这意味着您无法在不更改另一个层的情况下更改一个层。 (2认同)
  • @EduardoRonaldo 在小型应用程序中,这不是问题。但随着应用程序的增长,您希望分离数据、服务和表示层,以便可以独立开发和发展它们。这意味着,如果您的数据层生成投影 DTO,服务层应该使用它,但不应该将其发送到表示层:该层应该对您的数据层一无所知——对存储库/实体/一无所知。它使用的投影类。只有这样,您才能更改数据层而不影响表示层。 (2认同)

Tan*_*ury 5

定义一个自定义pojo类,例如sureveyQueryAnalytics并将查询返回的值存储在您的自定义pojo类中

@Query(value = "select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer")
List<SureveyQueryAnalytics> calculateSurveyCount();
Run Code Online (Sandbox Code Playgroud)

  • 解决方案比较好。或者使用[官方文档中的投影](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections) (2认同)