java—如何通过查询从SpringDataJPA组返回自定义对象

ercv8c1e  于 2021-07-06  发布在  Java
关注(0)|答案(9)|浏览(375)

我正在用springdatajpa开发一个spring引导应用程序。我正在使用一个定制的jpql查询按某个字段分组并获取计数。下面是我的存储库方法。

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

其工作和结果如下:

[
  [1, "a1"],
  [2, "a2"]
]

我想得到这样的东西:

[
  { "cnt":1, "answer":"a1" },
  { "cnt":2, "answer":"a2" }
]

我怎样才能做到这一点?

bkhjykvo

bkhjykvo1#

我使用自定义dto(接口)将本机查询Map到—这是最灵活、最安全的重构方法。
我遇到的问题是,令人惊讶的是,接口中字段的顺序和查询中列的顺序很重要。我通过按字母顺序排列接口getter,然后以同样的方式排列查询中的列来实现它。

4ioopgfo

4ioopgfo2#

我知道这是一个老生常谈的问题,它已经得到了回答,但这里有另一种方法:

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

jhdbpxl93#

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;
  }
}

步骤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();
}

重要注意事项

确保提供bean类的完全限定路径,包括包名。例如,如果调用bean类 MyBean 而且是打包的 com.path.to ,指向bean的完全限定路径将是 com.path.to.MyBean . 只是提供 MyBean 不会工作(除非bean类在默认包中)。
确保使用 new 关键字。 SELECT new com.path.to.MyBean(...) 会有用的,但是 SELECT com.path.to.MyBean(...) 不会的。
确保传递属性的顺序与bean构造函数中预期的顺序完全相同。尝试以不同的顺序传递属性将导致异常。
确保查询是有效的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标准的一部分。
在这样的情况下,bean类需要用spring数据投影接口替换。
步骤1:声明投影接口

package com.path.to;

public interface SurveyAnswerStatistics {
  String getAnswer();

  int getCnt();
}

步骤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();
}

使用sql AS 关键字将结果字段Map到投影属性以进行明确Map。

yvt65v4c

yvt65v4c4#

我不喜欢查询字符串中的java类型名,并用特定的构造函数来处理它。spring jpa在hashmap参数中使用查询结果隐式调用构造函数:

@Getter
public class SurveyAnswerStatistics {
  public static final String PROP_ANSWER = "answer";
  public static final String PROP_CNT = "cnt";

  private String answer;
  private Long   cnt;

  public SurveyAnswerStatistics(HashMap<String, Object> values) {
    this.answer = (String) values.get(PROP_ANSWER);
    this.count  = (Long) values.get(PROP_CNT);
  }
}

@Query("SELECT v.answer as "+PROP_ANSWER+", count(v) as "+PROP_CNT+" FROM  Survey v GROUP BY v.answer")
List<SurveyAnswerStatistics> findSurveyCount();

代码需要lombok来解析@getter

js4nwp54

js4nwp545#

@Repository
public interface ExpenseRepo extends JpaRepository<Expense,Long> {
    List<Expense> findByCategoryId(Long categoryId);

    @Query(value = "select category.name,SUM(expense.amount) from expense JOIN category ON expense.category_id=category.id GROUP BY expense.category_id",nativeQuery = true)
    List<?> getAmountByCategory();

}

上面的代码对我有用。

tgabmvqs

tgabmvqs6#

我刚刚解决了这个问题:
基于类的投影不适用于本机查询( @Query(value = "SELECT ...", nativeQuery = true ))因此,我建议使用接口定义自定义dto。
在使用dto之前,应该验证查询的语法是否正确

yeotifhr

yeotifhr7#

定义一个定制的pojo类say 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();
jfgube3f

jfgube3f8#

此sql查询返回列表<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;
     }
 }
dphi5xsq

dphi5xsq9#

使用接口可以得到更简单的代码。不需要创建和手动调用构造函数
步骤1:用必需字段声明intefrace:

public interface SurveyAnswerStatistics {

  String getAnswer();
  Long getCnt();

}

步骤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();

}

相关问题