Je développe une application Spring Boot avec Spring Data JPA. J'utilise une requête JPQL personnalisée pour regrouper certains champs et obtenir le nombre. Voici ma méthode de référentiel.
@Query(value = "select count(v) as cnt, v.answer from Survey v group by v.answer")
public List<?> findSurveyCount();
Cela fonctionne et le résultat est obtenu comme suit:
[
[1, "a1"],
[2, "a2"]
]
J'aimerais avoir quelque chose comme ça:
[
{ "cnt":1, "answer":"a1" },
{ "cnt":2, "answer":"a2" }
]
Comment puis-je atteindre cet objectif?
Ceci est pris en charge pour les requêtes JPQL au sein de spécification JPA .
Étape 1 : Déclarez une classe de beans 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;
}
}
Étape 2 : Renvoyez les instances de bean à partir de la méthode de référentiel.
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
et qu'elle se trouve dans le package com.path.to
, le chemin qualifié complet menant au bean sera com.path.to.MyBean
. Fournir simplement MyBean
ne fonctionnera pas (sauf si la classe de bean est dans le paquet par défaut).new
. SELECT new com.path.to.MyBean(...)
fonctionnera, alors que SELECT com.path.to.MyBean(...)
ne fonctionnera pas.@Query("SELECT ...")
ou @Query(value = "SELECT ...")
ou @Query(value = "SELECT ...", nativeQuery = false)
fonctionnera, alors que @Query(value = "SELECT ...", nativeQuery = true)
ne fonctionnera pas. En effet, les requêtes natives sont transmises sans modification au fournisseur JPA et sont exécutées sur le SGBDR sous-jacent en tant que tel. Puisque new
et com.path.to.MyBean
ne sont pas des mots-clés SQL valides, le SGBDR lève alors une exception.Comme indiqué ci-dessus, la syntaxe new ...
est un mécanisme pris en charge par JPA et fonctionne avec tous les fournisseurs JPA. Toutefois, si la requête elle-même n’est pas une requête JPA, c’est-à-dire qu’elle est une requête native, la syntaxe new ...
ne fonctionnera pas car la requête est transmise directement au SGBDR sous-jacent, qui ne comprend pas le new
mot-clé puisqu'il ne fait pas partie du standard SQL.
Dans de telles situations, les classes de beans doivent être remplacées par des interfaces Spring Data Projection .
Étape 1 : Déclarez une interface de projection
package com.path.to;
public interface SurveyAnswerStatistics {
String getAnswer();
int getCnt();
}
Étape 2 : Renvoyer les propriétés projetées à partir de la requête.
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();
}
Utilisez le mot clé SQL AS
pour mapper les champs de résultat sur les propriétés de projection pour un mappage non ambigu.
Cette requête SQL renvoie List <Object []>.
Vous pouvez le faire de cette façon:
@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;
}
}
Je sais que c'est une vieille question à laquelle il a déjà été répondu, mais voici une autre approche:
@Query("select new map(count(v) as cnt, v.answer) from Survey v group by v.answer")
public List<?> findSurveyCount();
En utilisant des interfaces, vous pouvez obtenir un code plus simple. Pas besoin de créer et d'appeler manuellement des constructeurs
Étape 1: Déclarez intefrace avec les champs obligatoires:
public interface SurveyAnswerStatistics {
String getAnswer();
Long getCnt();
}
Étape 2: Sélectionnez les colonnes portant le même nom que getter dans l'interface et renvoyez intefrace à partir de la méthode de référentiel:
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();
}
définir une classe pojo personnalisée, par exemple sureveyQueryAnalytics, et stocker la valeur renvoyée par la requête dans votre classe pojo personnalisée
@Query(value = "select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer")
List<SureveyQueryAnalytics> calculateSurveyCount();
Je n'aime pas les noms de type Java dans les chaînes de requête et les gère avec un constructeur spécifique. Spring JPA appelle implicitement le constructeur avec le résultat de la requête dans le paramètre 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();
Le code a besoin de Lombok pour résoudre @Getter
Je viens de résoudre ce problème:
@Query(value = "SELECT ...", nativeQuery = true
)). Il est donc recommandé de définir un DTO personnalisé à l'aide d'une interface.