How to return a custom object from a Spring Data JPA GROUP BY query

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



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. 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?




5 Answers
5



Solution for JPQL queries



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


com.path.to


com.path.to.MyBean


MyBean


new


SELECT new com.path.to.MyBean(...)


SELECT com.path.to.MyBean(...)


@Query("SELECT ...")


@Query(value = "SELECT ...")


@Query(value = "SELECT ...", nativeQuery = false)


@Query(value = "SELECT ...", nativeQuery = true)


new


com.path.to.MyBean



Solution for native queries



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.


new ...


new ...


new



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.


AS





It's not working, firing error : Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate class [SurveyAnswerReport] [select new SurveyAnswerReport(v.answer,count(v.id)) from com.furniturepool.domain.Survey v group by v.answer] at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1750) at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677) at org.hibernate.jpa.spi.AbstractEnti..........
– Pranav C Balan
Mar 31 '16 at 9:45


Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate class [SurveyAnswerReport] [select new SurveyAnswerReport(v.answer,count(v.id)) from com.furniturepool.domain.Survey v group by v.answer] at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1750) at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677) at org.hibernate.jpa.spi.AbstractEnti..........





What is this SurveyAnswerReport in your output. I assume you replaced SurveyAnswerStatistics with your own class SurveyAnswerReport. You need to specify the fully qualified class name.
– Bunti
Mar 31 '16 at 9:49


SurveyAnswerReport


SurveyAnswerStatistics


SurveyAnswerReport





The bean class must be fully qualified, that is, include the full package name. Something like com.domain.dto.SurveyAnswerReport.
– manish
Mar 31 '16 at 9:50


com.domain.dto.SurveyAnswerReport





@manish : thanks man, it's working now :)
– Pranav C Balan
Mar 31 '16 at 10:46





I got 'java.lang.IllegalArgumentException: PersistentEntity must not be null!` when i try return custom type from my JpaRepository ? Is some configuration i missed ?
– marioosh
Oct 16 '17 at 9:12


JpaRepository



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;






thanks for your response :)
– Pranav C Balan
Mar 31 '16 at 10:47





thanks for your response to this question. It was crisp and clear
– Dheeraj R
Jul 11 '17 at 0:44





@PranavCBalan thank you :)
– ozgur
Nov 16 '17 at 12:55





@DheerajR thank you :)
– ozgur
Nov 16 '17 at 12:55





@aswzen you are welcome
– ozgur
Jun 11 at 5:31



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



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



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






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard