How to return a custom object from a Spring Data JPA GROUP BY query
我正在使用Spring Data JPA开发Spring Boot应用程序。 我正在使用自定义JPQL查询来按某个字段分组并获取计数。 以下是我的存储库方法。
1 2 | @Query(value ="select count(v) as cnt, v.answer from Survey v group by v.answer") public List< ? > findSurveyCount(); |
它正在工作,结果如下:
1 2 3 4 | [ [1,"a1"], [2,"a2"] ] |
我想得到这样的东西:
1 2 3 4 | [ {"cnt":1,"answer":"a1" }, {"cnt":2,"answer":"a2" } ] |
我该如何实现?
JPQL查询解决方案
JPA规范中的JPQL查询支持此功能。
Step 1: Declare a simple bean class
1 2 3 4 5 6 7 8 9 10 11 |
Step 2: Return bean instances from the repository method
1 2 3 4 5 6 7 8 9 | 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(); } |
重要笔记
本机查询解决方案
如上所述,
在这种情况下,需要用Spring Data Projection接口替换bean类。
Step 1: Declare a projection interface
1 2 3 4 5 6 7 |
Step 2: Return projected properties from the query
1 2 3 4 5 6 7 8 9 10 | 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
此SQL查询返回List
您可以这样操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @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; } } |
我知道这是一个古老的问题,并且已经得到解答,但这是另一种方法:
1 2 | @Query("select new map(count(v) as cnt, v.answer) from Survey v group by v.answer") public List< ? > findSurveyCount(); |
使用接口,您可以获得更简单的代码。无需创建和手动调用构造函数
步骤1:使用必填字段声明界面:
步骤2:在接口中选择与getter名称相同的列,然后从存储库方法返回intefrace:
1 2 3 4 5 6 7 8 | 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(); } |
定义一个自定义pojo类,例如sureveyQueryAnalytics并将查询返回的值存储在自定义pojo类中
1 2 | @Query(value ="select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer") List<SureveyQueryAnalytics> calculateSurveyCount(); |
我不喜欢查询字符串中的Java类型名称,并使用特定的构造函数来处理它。
Spring JPA在HashMap参数中隐式调用具有查询结果的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @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
我使用自定义DTO(接口)将本机查询映射到-最灵活的方法和重构安全的方法。
我遇到的问题-令人惊讶的是,界面中字段的顺序和查询中的列很重要。我通过按字母顺序对接口吸气剂进行排序,然后以相同的方式对查询中的列进行排序来使其工作。
我刚刚解决了这个问题:
-
基于类的投影不适用于本机查询(
@Query(value ="SELECT ...", nativeQuery = true )),因此我建议使用interface定义自定义DTO。 - 在使用DTO之前,应在语法上验证查询是否正确
1 2 3 4 5 6 7 8 | @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(); } |
上面的代码为我工作。