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

时间:2016-03-31 08:29:53

标签: java spring spring-mvc jpa 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();

它正常工作,结果如下:

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

我想得到这样的东西:

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

我怎样才能做到这一点?

11 个答案:

答案 0 :(得分:162)

JPQL查询的解决方案

JPA specification中的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();
}

重要说明

  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执行。由于newcom.path.to.MyBean不是有效的SQL关键字,因此RDBMS会抛出异常。
  5. 原生查询的解决方案

    如上所述,new ...语法是JPA支持的机制,适用于所有JPA提供程序。但是,如果查询本身不是JPA查询,即它是本机查询,则new ...语法将无效,因为查询将直接传递给基础RDBMS,后者无法理解{{ 1}}关键字,因为它不是SQL标准的一部分。

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

      

    第1步:声明投影界面

    new
      

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

    package com.path.to;
    
    public interface SurveyAnswerStatistics {
      String getAnswer();
    
      int getCnt();
    }
    

    使用SQL 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(); } 关键字将结果字段映射到投影属性以进行明确映射。

答案 1 :(得分:16)

此SQL查询返回List&lt;对象[]&gt;会。

你可以这样做:

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

答案 2 :(得分:8)

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

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

答案 3 :(得分:6)

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

第1步:声明必填项并填写必填字段:

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

}

答案 4 :(得分:4)

定义一个自定义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();

答案 5 :(得分:2)

我不喜欢查询字符串中的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

答案 6 :(得分:1)

我使用自定义DTO(接口)将本机查询映射到-最灵活的方法和重构安全的

我遇到的问题-令人惊讶的是,界面中字段的顺序和查询中列的顺序很重要。我通过按字母顺序对接口吸气剂进行排序,然后以相同的方式对查询中的列进行排序来使其工作。

答案 7 :(得分:1)

使用 JDBC 获取包含列名及其值(在键值对中)的数据:

/*Template class with a basic set of JDBC operations, allowing the use
  of named parameters rather than traditional '?' placeholders.
 
  This class delegates to a wrapped {@link #getJdbcOperations() JdbcTemplate}
  once the substitution from named parameters to JDBC style '?' placeholders is
  done at execution time. It also allows for expanding a {@link java.util.List}
  of values to the appropriate number of placeholders.
 
  The underlying {@link org.springframework.jdbc.core.JdbcTemplate} is
  exposed to allow for convenient access to the traditional
  {@link org.springframework.jdbc.core.JdbcTemplate} methods.*/


@Autowired
protected  NamedParameterJdbcTemplate jdbc;


@GetMapping("/showDataUsingQuery/{Query}")
    public List<Map<String,Object>> ShowColumNameAndValue(@PathVariable("Query")String Query) throws SQLException {

      /* MapSqlParameterSource class is intended for passing in a simple Map of parameter values
        to the methods of the {@link NamedParameterJdbcTemplate} class*/

       MapSqlParameterSource msp = new MapSqlParameterSource();

       // this query used for show column name and columnvalues....
        List<Map<String,Object>> css = jdbc.queryForList(Query,msp);

        return css;
    }

答案 8 :(得分:0)

我刚刚解决了这个问题:

  • 基于类的投影不适用于本机查询(@Query(value = "SELECT ...", nativeQuery = true)),因此,我建议使用interface定义自定义DTO。
  • 在使用DTO之前,应先从语法上验证查询是否正确

答案 9 :(得分:0)

@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();

}

上面的代码对我有用。

答案 10 :(得分:0)

    //in Service      
      `
                public List<DevicesPerCustomer> findDevicesPerCustomer() {
                    LOGGER.info(TAG_NAME + " :: inside findDevicesPerCustomer : ");
                    List<Object[]> list = iDeviceRegistrationRepo.findDevicesPerCustomer();
                    List<DevicesPerCustomer> out = new ArrayList<>();
                    if (list != null && !list.isEmpty()) {
                        DevicesPerCustomer mDevicesPerCustomer = null;
                        for (Object[] object : list) {
                            mDevicesPerCustomer = new DevicesPerCustomer();
mDevicesPerCustomer.setCustomerId(object[0].toString());
                            mDevicesPerCustomer.setCount(Integer.parseInt(object[1].toString()));
                            
                            out.add(mDevicesPerCustomer);
                        }
                    }
            
                    return out;
                }`
        
    //In Repo
        `   @Query(value = "SELECT d.customerId,count(*) FROM senseer.DEVICE_REGISTRATION d  where d.customerId is not null group by d.customerId", nativeQuery=true)
            List<Object[]> findDevicesPerCustomer();`