Cómo devolver un objeto personalizado desde una consulta Spring Data JPA GROUP BY

Estoy desarrollando una aplicación Spring Boot con Spring Data JPA. Estoy usando una consulta JPQL personalizada para agrupar por algún campo y obtener el conteo. A continuación está mi método de repository.

@Query(value = "select count(v) as cnt, v.answer from Survey v group by v.answer") public List findSurveyCount(); 

Está funcionando y el resultado se obtiene de la siguiente manera:

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

Me gustaría obtener algo como esto:

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

¿Cómo puedo conseguir esto?

Solución para consultas JPQL

Esto es compatible con consultas JPQL dentro de la especificación JPA .

Paso 1 : declara una clase de bean simple

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

Paso 2 : Devolver las instancias de beans desde el método de repository

 public interface SurveyRepository extends CrudRepository { @Query("SELECT " + " new com.path.to.SurveyAnswerStatistics(v.answer, COUNT(v)) " + "FROM " + " Survey v " + "GROUP BY " + " v.answer") List findSurveyCount(); } 

Notas importantes

  1. Asegúrese de proporcionar la ruta de acceso completa a la clase de bean, incluido el nombre del paquete. Por ejemplo, si la clase de bean se llama MyBean y está en el paquete com.path.to , la ruta completa al bean será com.path.to.MyBean . Simplemente proporcionar MyBean no funcionará (a menos que la clase de bean esté en el paquete predeterminado).
  2. Asegúrese de llamar al constructor de la clase bean con la new palabra clave. SELECT new com.path.to.MyBean(...) funcionará, mientras que SELECT com.path.to.MyBean(...) no funcionará.
  3. Asegúrese de pasar los atributos exactamente en el mismo orden que el esperado en el constructor de bean. Intentar pasar atributos en un orden diferente dará lugar a una excepción.
  4. Asegúrese de que la consulta sea una consulta JPA válida, es decir, no es una consulta nativa. @Query("SELECT ...") , o @Query(value = "SELECT ...") , o @Query(value = "SELECT ...", nativeQuery = false) funcionará, mientras que @Query(value = "SELECT ...", nativeQuery = true) no funcionará. Esto se debe a que las consultas nativas se pasan sin modificaciones al proveedor de JPA y se ejecutan contra el RDBMS subyacente como tal. Como new y com.path.to.MyBean no son palabras clave SQL válidas, el RDBMS arroja una excepción.

Solución para consultas nativas

Como se señaló anteriormente, la new ... syntax es un mecanismo compatible con JPA y funciona con todos los proveedores de JPA. Sin embargo, si la consulta en sí no es una consulta JPA, es decir, es una consulta nativa, la new ... syntax no funcionará ya que la consulta se pasa directamente al RDBMS subyacente, que no comprende la new palabra clave desde no es parte del estándar SQL.

En situaciones como estas, las clases de bean deben reemplazarse con las interfaces de Spring Data Projection .

Paso 1 : Declarar una interfaz de proyección

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

Paso 2 : devolver las propiedades proyectadas de la consulta

 public interface SurveyRepository extends CrudRepository { @Query(nativeQuery = true, value = "SELECT " + " v.answer AS answer, COUNT(v) AS cnt " + "FROM " + " Survey v " + "GROUP BY " + " v.answer") List findSurveyCount(); } 

Use la palabra clave SQL AS para asignar los campos de resultados a las propiedades de proyección para una asignación inequívoca.

Esta consulta de SQL devuelve List would.

Puedes hacerlo de esta manera:

  @RestController @RequestMapping("/survey") public class SurveyController { @Autowired private SurveyRepository surveyRepository; @RequestMapping(value = "/find", method = RequestMethod.GET) public Map findSurvey(){ List result = surveyRepository.findSurveyCount(); Map map = null; if(result != null && !result.isEmpty()){ map = new HashMap(); for (Object[] object : result) { map.put(((Long)object[0]),object[1]); } } return map; } } 

Sé que esta es una pregunta antigua y ya ha sido respondida, pero aquí hay otro enfoque:

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

defina una clase de pojo personalizada que diga sureveyQueryAnalytics y almacene el valor devuelto de la consulta en su clase de pojo personalizada

 @Query(value = "select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer") List calculateSurveyCount(); 

No me gustan los nombres de tipos de Java en las cadenas de consulta y lo manejo con un constructor específico. Spring JPA llama implícitamente al constructor con el resultado de la consulta en el parámetro 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 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 findSurveyCount();