Reputation: 115212
I'm developing a Spring Boot application with Spring Data JPA. I'm using a custom JPQL query to group by some field and get the count. 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?
Upvotes: 182
Views: 324152
Reputation: 20135
This is supported for JPQL queries within the JPA specification.
Step 1: Declare a simple bean class
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
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
. Simply providing MyBean
will not work (unless the bean class is in the default package).new
keyword. SELECT new com.path.to.MyBean(...)
will work, whereas SELECT com.path.to.MyBean(...)
will not.@Query("SELECT ...")
, or @Query(value = "SELECT ...")
, or @Query(value = "SELECT ...", nativeQuery = false)
will work, whereas @Query(value = "SELECT ...", nativeQuery = true)
will not work. This is because native queries are passed without modifications to the JPA provider, and are executed against the underlying RDBMS as such. Since new
and com.path.to.MyBean
are not valid SQL keywords, the RDBMS then throws an exception.As noted above, the new ...
syntax is a JPA-supported mechanism and works with all JPA providers. 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.
In situations like these, bean classes need to be replaced with Spring Data Projection interfaces.
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
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.
Upvotes: 384
Reputation: 21
//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();`
Upvotes: 0
Reputation: 7
Get data with column name and its values (in key-value pair) using 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;
}
Upvotes: 1
Reputation: 1837
@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.
Upvotes: 3
Reputation: 1125
I used custom DTO (interface) to map a native query to - the most flexible approach and refactoring-safe.
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.
Upvotes: 2
Reputation: 402
I just solved this problem :
@Query(value = "SELECT ...", nativeQuery = true
)) so I recommend to define custom DTO using interface . Upvotes: 1
Reputation: 91
I do not like java type names in query strings and handle it with a specific constructor. Spring JPA implicitly calls constructor with query result in HashMap parameter:
@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
Upvotes: 4
Reputation: 1278
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();
Upvotes: 24
Reputation: 2445
define a custom pojo class say sureveyQueryAnalytics and store the query returned value in your custom pojo class
@Query(value = "select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer")
List<SureveyQueryAnalytics> calculateSurveyCount();
Upvotes: 7
Reputation: 3466
This SQL query return List< Object[] > would.
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;
}
}
Upvotes: 29