springframework query org jparepository data crudrepository java spring spring-mvc jpa repository

java - query - Cómo devolver un objeto personalizado de una consulta Spring Data JPA GROUP BY



org springframework data jpa repository query (7)

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 instancias de bean desde el método de repositorio

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

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 de acceso completa al bean será com.path.to.MyBean . Simplemente proporcionar MyBean no funcionará (a menos que la clase bean esté en el paquete predeterminado).
  2. Asegúrese de llamar al constructor de la clase bean utilizando la new palabra clave. SELECT new com.path.to.MyBean(...) funcionará, mientras que SELECT com.path.to.MyBean(...) no lo hará.
  3. Asegúrese de pasar los atributos exactamente en el mismo orden que el esperado en el constructor de beans. 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, que no sea 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 en el RDBMS subyacente como tal. Dado que new y com.path.to.MyBean no son palabras clave válidas de SQL, RDBMS com.path.to.MyBean una excepción.

Solución para consultas nativas

Como se señaló anteriormente, la new ... sintaxis 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 ... sintaxis new ... 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 necesitan ser reemplazadas por interfaces Spring Data Projection .

Paso 1 : declara 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<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 la palabra clave SQL AS para asignar campos de resultados a propiedades de proyección para una asignación inequívoca.

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 recuento. El siguiente es mi método de repositorio.

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


Esta consulta SQL devuelve List <Object []> would.

Puedes hacerlo de esta manera:

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


No me gustan los nombres de tipo 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<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 necesita Lombok para resolver @Getter


Sé que esta es una vieja pregunta 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();


Usando interfaces puede obtener un código más simple. No es necesario crear y llamar manualmente a los constructores.

Paso 1 : declara intefrace con los campos obligatorios:

public interface SurveyAnswerStatistics { String getAnswer(); Long getCnt(); }

Paso 2 : Seleccione columnas con el mismo nombre que getter en la interfaz y devuelva intefrace desde el método de repositorio:

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


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

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


Acabo de resolver este problema:

  • Las proyecciones basadas en clases no funcionan con la consulta nativa ( @Query(value = "SELECT ...", nativeQuery = true )), por lo que recomiendo definir DTO personalizado mediante la interfaz.
  • Antes de usar DTO debe verificar la consulta sintácticamente correcta o no