[英]How to return a custom object from a Spring Data JPA GROUP BY query
I'm developing a Spring Boot application with Spring Data JPA.我正在使用 Spring Data JPA 开发 Spring Boot 应用程序。 I'm using a custom JPQL query to group by some field and get the count.
我正在使用自定义 JPQL 查询按某个字段分组并获取计数。 Following is my repository method.
以下是我的存储库方法。
@Query(value = "select count(v) as cnt, v.answer from Survey v group by v.answer")
public List<?> findSurveyCount();
It's working and result is obtained as follows:它正在工作,结果如下:
[
[1, "a1"],
[2, "a2"]
]
I would like to get something like this:我想得到这样的东西:
[
{ "cnt":1, "answer":"a1" },
{ "cnt":2, "answer":"a2" }
]
How can I achieve this?我怎样才能做到这一点?
This is supported for JPQL queries within the JPA specification . JPA 规范中的JPQL 查询支持此功能。
Step 1 : Declare a simple bean class
第 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;
}
}
Step 2 : Return bean instances from the repository method
第 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();
}
MyBean
and it is in package com.path.to
, the fully-qualified path to the bean will be com.path.to.MyBean
.MyBean
并且它在包com.path.to
,则 bean 的完全限定路径将是com.path.to.MyBean
。 Simply providing MyBean
will not work (unless the bean class is in the default package).MyBean
行不通的(除非 bean 类在默认包中)。new
keyword.new
关键字调用 bean 类构造函数。 SELECT new com.path.to.MyBean(...)
will work, whereas SELECT com.path.to.MyBean(...)
will not. SELECT new com.path.to.MyBean(...)
会起作用,而SELECT com.path.to.MyBean(...)
不会。@Query("SELECT ...")
, or @Query(value = "SELECT ...")
, or @Query(value = "SELECT ...", nativeQuery = false)
will work, whereas @Query(value = "SELECT ...", nativeQuery = true)
will not work. @Query("SELECT ...")
或@Query(value = "SELECT ...")
或@Query(value = "SELECT ...", nativeQuery = false)
将起作用,而@Query(value = "SELECT ...", nativeQuery = true)
将不起作用。 This is because native queries are passed without modifications to the JPA provider, and are executed against the underlying RDBMS as such.new
and com.path.to.MyBean
are not valid SQL keywords, the RDBMS then throws an exception.new
和com.path.to.MyBean
不是有效的 SQL 关键字,因此 RDBMS 会抛出异常。As noted above, the new ...
syntax is a JPA-supported mechanism and works with all JPA providers.如上所述,
new ...
语法是 JPA 支持的机制,适用于所有 JPA 提供程序。 However, if the query itself is not a JPA query, that is, it is a native query, the new ...
syntax will not work as the query is passed on directly to the underlying RDBMS, which does not understand the new
keyword since it is not part of the SQL standard.但是,如果查询本身是不是一个JPA查询,也就是说,它是一种天然的查询时,
new ...
语法不会工作作为查询直接传递到底层的RDBMS,这并不了解new
,因为关键字它不是 SQL 标准的一部分。
In situations like these, bean classes need to be replaced with Spring Data Projection interfaces.在这种情况下,需要用Spring Data Projection接口替换 bean 类。
Step 1 : Declare a projection interface
第一步:声明一个投影接口
package com.path.to;
public interface SurveyAnswerStatistics {
String getAnswer();
int getCnt();
}
Step 2 : Return projected properties from the query
第 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();
}
Use the SQL AS
keyword to map result fields to projection properties for unambiguous mapping.使用 SQL
AS
关键字将结果字段映射到投影属性以进行明确映射。
This SQL query return List< Object[] > would.此 SQL 查询将返回 List<Object[]>。
You can do it this way:你可以这样做:
@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;
}
}
I know this is an old question and it has already been answered, but here's another approach:我知道这是一个老问题,已经有人回答了,但这是另一种方法:
@Query("select new map(count(v) as cnt, v.answer) from Survey v group by v.answer")
public List<?> findSurveyCount();
Using interfaces you can get simpler code.使用接口可以获得更简单的代码。 No need to create and manually call constructors
无需创建和手动调用构造函数
Step 1 : Declare interface with the required fields:第 1 步:声明具有必填字段的接口:
public interface SurveyAnswerStatistics {
String getAnswer();
Long getCnt();
}
Step 2 : Select columns with same name as getter in interface and return intefrace from repository method:第 2 步:在接口中选择与 getter 同名的列并从存储库方法返回接口:
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();
}
define a custom pojo class say sureveyQueryAnalytics and store the query returned value in your custom pojo class定义一个自定义 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();
I do not like java type names in query strings and handle it with a specific constructor.我不喜欢查询字符串中的 java 类型名称并使用特定的构造函数处理它。 Spring JPA implicitly calls constructor with query result in HashMap parameter:
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();
Code needs Lombok for resolving @Getter代码需要 Lombok 来解析 @Getter
I used custom DTO (interface) to map a native query to - the most flexible approach and refactoring-safe.我使用自定义 DTO(接口)将本机查询映射到 - 最灵活的方法和重构安全。
The problem I had with this - that surprisingly, the order of fields in the interface and the columns in the query matters.我遇到的问题 - 令人惊讶的是,界面中的字段顺序和查询中的列很重要。 I got it working by ordering interface getters alphabetically and then ordering the columns in the query the same way.
我通过按字母顺序对接口 getter 进行排序,然后以相同的方式对查询中的列进行排序来使其工作。
@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();
}
The above code worked for me.上面的代码对我有用。
Get data with column name and its values (in key-value pair) using JDBC:使用 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;
}
I just solved this problem :我刚刚解决了这个问题:
@Query(value = "SELECT ...", nativeQuery = true
)) so I recommend to define custom DTO using interface .@Query(value = "SELECT ...", nativeQuery = true
)) 所以我建议使用 interface 定义自定义 DTO。 //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();`
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.