Рубрики
Без рубрики

Настройка результатов запросов JPA с помощью функций агрегирования

Spring Data JPA имеет некоторые полезные приемы для сопоставления результатов запросов агрегации с пользовательскими объектами, а не с массивами объектов по умолчанию.

Автор оригинала: baeldung.

1. Обзор

В то время как Spring Data JPA может абстрагироваться от создания запросов для извлечения сущностей из базы данных в определенных ситуациях, иногда нам необходимо настроить наши запросы, например, при добавлении функций агрегации .

В этом уроке мы сосредоточимся на том, как преобразовать результаты этих запросов в объект. Мы рассмотрим два различных решения — одно с использованием спецификации JPA и POJO, а другое с использованием проекции данных Spring.

2. Запросы JPA и проблема агрегации

Запросы JPA обычно выдают свои результаты в виде экземпляров сопоставленной сущности. Однако запросы с функциями агрегации обычно возвращают результат в виде Object[] .

Чтобы понять проблему, давайте определим модель домена, основанную на взаимосвязи между сообщениями и комментариями:

@Entity
public class Post {
    @Id
    private Integer id;
    private String title;
    private String content;
    @OneToMany(mappedBy = "post")
    private List comments;

    // additional properties
    // standard constructors, getters, and setters
}

@Entity
public class Comment {
    @Id
    private Integer id;
    private Integer year;
    private boolean approved;
    private String content;
    @ManyToOne
    private Post post;

    // additional properties
    // standard constructors, getters, and setters
}

Наша модель определяет, что сообщение может иметь много комментариев, и каждый комментарий принадлежит одному сообщению. Давайте использовать хранилище данных Spring с этой моделью:

@Repository
public interface CommentRepository extends JpaRepository {
    // query methods
}

Теперь давайте посчитаем комментарии, сгруппированные по годам:

@Query("SELECT c.year, COUNT(c.year) FROM Comment AS c GROUP BY c.year ORDER BY c.year DESC")
List countTotalCommentsByYear();

Результат предыдущего запроса JPA не может быть загружен в экземпляр Comment, , поскольку результат имеет другую форму. | Год и КОЛИЧЕСТВО , указанные в запросе, не соответствуют нашему объекту сущности.

Хотя мы все еще можем получить доступ к результатам в объекте общего назначения Object [] , возвращенном в списке, это приведет к беспорядочному, подверженному ошибкам коду.

3. Настройка результата с помощью конструкторов классов

Спецификация JPA позволяет нам настраивать результаты объектно-ориентированным способом. Поэтому мы можем использовать выражение конструктора JPQL для задания результата:

@Query("SELECT new com.baeldung.aggregation.model.custom.CommentCount(c.year, COUNT(c.year)) "
  + "FROM Comment AS c GROUP BY c.year ORDER BY c.year DESC")
List countTotalCommentsByYearClass();

Это связывает выходные данные оператора SELECT с POJO. Указанный класс должен иметь конструктор, который точно соответствует проецируемым атрибутам, но он не обязательно должен быть аннотирован с помощью @Entity .

Мы также видим, что конструктор, объявленный в JPQL, должен иметь полное имя:

package com.baeldung.aggregation.model.custom;

public class CommentCount {
    private Integer year;
    private Long total;

    public CommentCount(Integer year, Long total) {
        this.year = year;
        this.total = total;
    }
    // getters and setters
}

4. Настройка результата с помощью проекции данных Spring

Другим возможным решением является настройка результата запросов JPA с помощью Spring Data Projection . Эта функциональность позволяет нам проецировать результаты запросов со значительно меньшим количеством кода .

4.1. Настройка результатов запросов JPA

Чтобы использовать проекцию на основе интерфейса, мы должны определить интерфейс Java, состоящий из методов получения, которые соответствуют именам проецируемых атрибутов. Давайте определим интерфейс для нашего результата запроса:

public interface ICommentCount {
    Integer getYearComment();
    Long getTotalComment();
}

Теперь давайте выразим наш запрос с результатом, возвращенным в виде List<Количество комментариев> :

@Query("SELECT c.year AS yearComment, COUNT(c.year) AS totalComment "
  + "FROM Comment AS c GROUP BY c.year ORDER BY c.year DESC")
List countTotalCommentsByYearInterface();

Чтобы позволить Spring привязывать проецируемые значения к нашему интерфейсу, нам нужно дать псевдонимы каждому проецируемому атрибуту с именем свойства, найденным в интерфейсе.

Затем Spring Data построит результат на лету и вернет экземпляр прокси для каждой строки результата.

4.2. Настройка результатов собственных запросов

Мы можем столкнуться с ситуациями, когда запросы JPA не так быстры, как собственный SQL, или не могут использовать некоторые специфические функции нашего ядра базы данных. Чтобы решить эту проблему, мы используем собственные запросы.

Одним из преимуществ проекции на основе интерфейса является то, что мы можем использовать ее для собственных запросов. Давайте снова используем Счетчик комментариев и свяжем его с SQL-запросом:

@Query(value = "SELECT c.year AS yearComment, COUNT(c.*) AS totalComment "
  + "FROM comment AS c GROUP BY c.year ORDER BY c.year DESC", nativeQuery = true)
List countTotalCommentsByYearNative();

Это работает аналогично запросам JPQL.

5. Заключение

В этой статье мы оценили два различных решения для сопоставления результатов запросов JPA с функциями агрегации. Во-первых, мы использовали стандарт JPA, включающий класс POJO, а во втором решении мы использовали легкие проекции данных Spring с интерфейсом.

Весенние прогнозы данных позволяют нам писать меньше кода, как на Java, так и на JPQL.

Как всегда, пример кода для этого урока доступен на GitHub .