Как вернуть пользовательский объект из запроса <JPY GROUP BY Spring
Я разрабатываю приложение загрузки Spring с Spring Data JPA. Я использую пользовательский запрос JPQL для группировки по некоторому полю и получения счета. Ниже приведен мой метод репозитория.
@Query(value = "select count(v) as cnt, v.answer from Survey v group by v.answer")
public List<?> findSurveyCount();
Работа и результат получаются следующим образом:
[
[1, "a1"],
[2, "a2"]
]
Я хотел бы получить что-то вроде этого:
[
{ "cnt":1, "answer":"a1" },
{ "cnt":2, "answer":"a2" }
]
Как я могу это достичь?
Ответы
Ответ 1
Решение для запросов JPQL
Это поддерживается для запросов JPQL в спецификации JPA.
Шаг 1. Объявите простой класс bean
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;
}
}
Шаг 2: Вернуть экземпляры bean-компонента из метода репозитория
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();
}
Важные заметки
- Обязательно укажите полный путь к классу компонентов, включая имя пакета. Например, если класс bean-класса называется
MyBean
и он находится в пакете com.path.to
, то полный путь к bean-компоненту будет com.path.to.MyBean
. Простое предоставление MyBean
не будет работать (если только класс bean не MyBean
в пакет по умолчанию). - Обязательно вызовите конструктор класса bean, используя
new
ключевое слово. SELECT new com.path.to.MyBean(...)
будет работать, тогда как SELECT com.path.to.MyBean(...)
не будет. - Обязательно передавайте атрибуты в том же порядке, что и в конструкторе bean. Попытка передать атрибуты в другом порядке приведет к исключению.
- Убедитесь, что запрос является допустимым запросом JPA, то есть он не является родным запросом.
@Query("SELECT...")
или @Query(value = "SELECT...")
, или @Query(value = "SELECT...", nativeQuery = false)
будет работать, тогда как @Query(value = "SELECT...", nativeQuery = true)
не будет работать. Это связано с тем, что исходные запросы передаются без изменений поставщику JPA и выполняются в соответствии с базовыми СУБД как таковыми. Поскольку new
и com.path.to.MyBean
не являются допустимыми ключевыми словами SQL, RDBMS затем генерирует исключение.
Решение для собственных запросов
Как отмечено выше, new...
синтаксис является JPA-поддерживаемым механизмом и работает со всеми поставщиками JPA. Однако, если сам запрос не является запросом JPA, то есть является родным запросом, new...
синтаксис не будет работать, поскольку запрос передается непосредственно в базовую RDBMS, которая не понимает new
ключевое слово, поскольку он не является частью стандарта SQL.
В подобных ситуациях классы bean необходимо заменить интерфейсами Spring Data Projection.
Шаг 1. Объявите интерфейс проекции
package com.path.to;
public interface SurveyAnswerStatistics {
String getAnswer();
int getCnt();
}
Шаг 2. Возвращаем проецируемые свойства из запроса.
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();
}
Используйте ключевое слово SQL AS
для сопоставления полей результатов с свойствами проецирования для однозначного сопоставления.
Ответ 2
Этот возвращаемый список запросов SQL < Объект [] > будет.
Вы можете сделать это следующим образом:
@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;
}
}
Ответ 3
Я знаю, что это старый вопрос, на который уже ответили, но здесь другой подход:
@Query("select new map(count(v) as cnt, v.answer) from Survey v group by v.answer")
public List<?> findSurveyCount();
Ответ 4
Используя интерфейсы, вы можете получить более простой код. Нет необходимости создавать и вручную вызывать конструкторы
Шаг 1: Объявите intefrace с обязательными полями:
public interface SurveyAnswerStatistics {
String getAnswer();
Long getCnt();
}
Шаг 2: Выберите столбцы с тем же именем, что и getter в интерфейсе и верните intefrace из метода репозитория:
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();
}
Ответ 5
определите пользовательский класс pojo, скажем sureveyQueryAnalytics, и сохраните возвращаемое значение запроса в вашем пользовательском классе pojo
@Query(value = "select new com.xxx.xxx.class.SureveyQueryAnalytics(s.answer, count(sv)) from Survey s group by s.answer")
List<SureveyQueryAnalytics> calculateSurveyCount();
Ответ 6
Мне не нравятся имена типов java в строках запроса и обрабатываются с помощью определенного конструктора. Spring JPA неявно вызывает конструктор с результатом запроса в параметре 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();
Код нуждается в Ломбоке для разрешения @Getter
Ответ 7
Я только что решил эту проблему:
- Проекции на основе классов не работают с собственным запросом (
@Query(value = "SELECT...", nativeQuery = true
)), поэтому я рекомендую определить собственный DTO с использованием интерфейса. - Перед использованием DTO следует проверить синтаксически правильный запрос или нет
Ответ 8
Я использовал собственный DTO (интерфейс), чтобы отобразить собственный запрос - самый гибкий подход и безопасный рефакторинг.
Проблема, с которой я столкнулся - удивительно, что порядок полей в интерфейсе и столбцов в запросе имеет значение. Я получил это, упорядочив получатели интерфейса в алфавитном порядке, а затем упорядочив столбцы в запросе таким же образом.