Подтвердить что ты не робот

Как вернуть пользовательский объект из запроса <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" }
]

Как я могу это достичь?

4b9b3361

Ответ 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();
}

Важные заметки

  1. Обязательно укажите полный путь к классу компонентов, включая имя пакета. Например, если класс bean-класса называется MyBean и он находится в пакете com.path.to, то полный путь к bean-компоненту будет com.path.to.MyBean. Простое предоставление MyBean не будет работать (если только класс bean не MyBean в пакет по умолчанию).
  2. Обязательно вызовите конструктор класса bean, используя new ключевое слово. SELECT new com.path.to.MyBean(...) будет работать, тогда как SELECT com.path.to.MyBean(...) не будет.
  3. Обязательно передавайте атрибуты в том же порядке, что и в конструкторе bean. Попытка передать атрибуты в другом порядке приведет к исключению.
  4. Убедитесь, что запрос является допустимым запросом 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 (интерфейс), чтобы отобразить собственный запрос - самый гибкий подход и безопасный рефакторинг.

Проблема, с которой я столкнулся - удивительно, что порядок полей в интерфейсе и столбцов в запросе имеет значение. Я получил это, упорядочив получатели интерфейса в алфавитном порядке, а затем упорядочив столбцы в запросе таким же образом.