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
-
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 paquetecom.path.to
, la ruta de acceso completa al bean serácom.path.to.MyBean
. Simplemente proporcionarMyBean
no funcionará (a menos que la clase bean esté en el paquete predeterminado). -
Asegúrese de llamar al constructor de la clase bean utilizando la
new
palabra clave.SELECT new com.path.to.MyBean(...)
funcionará, mientras queSELECT com.path.to.MyBean(...)
no lo hará. - 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.
-
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 quenew
ycom.path.to.MyBean
no son palabras clave válidas de SQL, RDBMScom.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