如何从 Spring Data JPAGROUPBY 查询返回自定义对象

我正在用 SpringDataJPA 开发一个 SpringBoot 应用程序。我正在使用一个自定义 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" }
]

我怎么才能做到呢?

266822 次浏览

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

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

重要提示

  1. 确保为 bean 类提供完全限定的路径,包括包名称。例如,如果 bean 类名为 MyBean,并且位于包 com.path.to中,那么 bean 的完全限定路径将是 com.path.to.MyBean。简单地提供 MyBean将不起作用(除非 bean 类在默认包中)。
  2. 确保使用 new关键字调用 bean 类构造函数。
  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 会抛出异常。

本机查询的解决方案

如上所述,new ...语法是一种受 JPA 支持的机制,可用于所有 JPA 提供程序。但是,如果查询本身不是 JPA 查询,也就是说,它是一个本机查询,那么 new ...语法将不起作用,因为查询将直接传递给底层 RDBMS,而底层 RDBMS 不理解 new关键字,因为它不是 SQL 标准的一部分。

在这种情况下,需要用 弹簧数据投影接口替换 bean 类。

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

使用 SQLAS关键字将结果字段映射到投影属性以进行明确的映射。

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

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

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

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

代码需要龙目岛来解析@Getter

我刚刚解决了这个问题:

  • 基于类的 Projections 不能与查询本机(@Query(value = "SELECT ...", nativeQuery = true)一起工作,因此我建议使用接口定义自定义 DTO。
  • 在使用 DTO 之前,应该验证查询的语法是否正确

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

我在这方面遇到的问题是——令人惊讶的是,接口中字段的顺序和查询中列的顺序很重要。我通过按字母顺序排列接口获取器,然后以相同的方式排列查询中的列,使它工作起来。

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


}

上面的代码对我很有用。

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


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