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

Проекции JPA/Hibernate

Узнайте, как делать прогнозы как в JPA, так и в режиме гибернации.

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

1. Обзор

В этом уроке мы узнаем, как проецировать свойства сущностей с помощью JPA и Hibernate .

2. Сущность

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

@Entity
public class Product {
    @Id
    private long id;
    
    private String name;
    
    private String description;
    
    private String category;
    
    private BigDecimal unitPrice;

    // setters and getters
}

Это простой класс сущностей, представляющий продукт с различными свойствами.

3. Проекции JPA

В спецификации JPA явно не упоминаются проекции, есть много случаев, когда мы находим их в концепции.

Как правило, запрос JPQL имеет класс сущностей-кандидатов. Запрос при выполнении создает объекты класса — кандидата, заполняя все их свойства с использованием полученных данных.

Но можно получить подмножество свойств сущности, или, то есть проекцию данных столбца.

Помимо данных столбцов, мы также можем проецировать результаты функций группировки.

3.1. Одноколоночные проекции

Предположим, мы хотим перечислить названия всех продуктов. В JPQL мы можем сделать это, включив только name в предложение select :

Query query = entityManager.createQuery("select name from Product");
List resultList = query.getResultList();

Или мы можем сделать то же самое с CriteriaBuilder :

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(String.class);
Root product = query.from(Product.class);
query.select(product.get("name"));
List resultList = entityManager.createQuery(query).getResultList();

Поскольку мы проецируем один столбец , который имеет тип String , мы ожидаем получить список String s в результате. Следовательно, мы указали класс-кандидат как String в методе CreateQuery () .

Поскольку мы хотим проецировать на одно свойство, мы использовали метод Query.select () . Здесь идет речь о том, какое свойство нам нужно, поэтому в нашем случае мы будем использовать имя свойство из нашего Продукта сущности.

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

Product Name 1
Product Name 2
Product Name 3
Product Name 4

Обратите внимание , что если бы мы использовали свойство id в проекции вместо name , запрос вернул бы список Long объектов.

3.2. Многоколоночные проекции

Чтобы спроецировать несколько столбцов с помощью JPQL, нам нужно только добавить все необходимые столбцы в предложение select :

Query query = session.createQuery("select id, name, unitPrice from Product");
List resultList = query.getResultList();

Но при использовании Построителя критериев нам придется действовать немного по-другому:

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Object[].class);
Root product = query.from(Product.class);
query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice"));
List resultList = entityManager.createQuery(query).getResultList();

Здесь/| мы использовали метод multiselect() вместо select() . Используя этот метод, мы можем указать несколько элементов для выбора.

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

Давайте посмотрим, как выглядят данные, когда мы их печатаем:

[1, Product Name 1, 1.40]
[2, Product Name 2, 4.30]
[3, Product Name 3, 14.00]
[4, Product Name 4, 3.90]

Как мы видим, возвращаемые данные немного громоздки для обработки. Но, к счастью, мы можем заставить JPA заполнить эти данные в пользовательский класс .

Кроме того, мы можем использовать CriteriaBuilder.tuple() или Criteriabuilder.construct() для получения результатов в виде списка объектов Tuple или объектов пользовательского класса соответственно.

3.3. Проектирование Агрегатных Функций

Помимо данных столбцов, иногда нам может потребоваться сгруппировать данные и использовать агрегатные функции, такие как count и average.

Допустим, мы хотим найти количество продуктов в каждой категории. Мы можем сделать это с помощью функции count() aggregate в JPQL:

Query query = entityManager.createQuery("select p.category, count(p) from Product p group by p.category");

Или мы можем использовать CriteriaBuilder :

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Object[].class);
Root product = query.from(Product.class);
query.multiselect(product.get("category"), builder.count(product));
query.groupBy(product.get("category"));

Здесь мы использовали метод Criteries Builder ‘s count () .

Использование любого из вышеперечисленных способов приведет к созданию списка массивов объектов:

[category1, 2]
[category2, 1]
[category3, 1]

Помимо count() , Criteria Builder предоставляет различные другие агрегатные функции:

  • avg – Вычисляет среднее значение для столбца в группе
  • max – Вычисляет максимальное значение для столбца в группе
  • min – Вычисляет минимальное значение для столбца в группе
  • наименьшее – Находит наименьшее значение столбца (например, в алфавитном порядке или по дате)
  • sum – Вычисляет сумму значений столбцов в группе

4. Спящий режим Проекций

В отличие от JPA, Hibernate предоставляет org.hibernate.criterion.Проекция для проектирования с запросом Критериев . Он также предоставляет класс с именем org.hibernate.criterion.Проекции, фабрика для Проекции экземпляров.

4.1. Одностолбчатые проекции

Во-первых, давайте посмотрим, как мы можем спроецировать один столбец. Мы будем использовать пример, который мы видели ранее:

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(Projections.property("name"));

Мы использовали метод Criteries.setProjection () , чтобы указать свойство, которое мы хотим получить в результате запроса. Projections.property() выполняет для нас ту же работу, что и Root.get() делал при указании столбца для выбора.

4.2. Многоколоночные проекции

Чтобы спроецировать несколько столбцов, нам сначала нужно создать список проекций . Список проекций – это особый вид Проекции , который обертывает другие проекции для выбора нескольких значений .

Мы можем создать Список проекций с помощью метода Projections.ProjectionList () , например, показать Продукт ‘s id и имя :

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(
  Projections.projectionList()
    .add(Projections.id())
    .add(Projections.property("name")));

4.3. Проектирование Агрегатных Функций

Как и CriteriaBuilder , класс Projections также предоставляет методы для агрегатных функций.

Давайте посмотрим, как мы можем реализовать пример подсчета, который мы видели ранее:

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(
  Projections.projectionList()
    .add(Projections.groupProperty("category"))
    .add(Projections.rowCount()));

Важно отметить, что мы не указывали напрямую ГРУППУ ПО в Критерии объект . Вызов свойства группы запускает это для нас.

Помимо функции rowCount () , Projections также предоставляет агрегатные функции, которые мы видели ранее.

4.4. Использование псевдонима для проекции

Интересной особенностью API критериев гибернации является использование псевдонима для проекции.

Это особенно полезно при использовании агрегатной функции, так как мы можем ссылаться на псевдоним в экземплярах Criterion и Order :

Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(Projections.projectionList()
             .add(Projections.groupProperty("category"))
             .add(Projections.alias(Projections.rowCount(), "count")));
criteria.addOrder(Order.asc("count"));

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

В этой статье мы рассмотрели, как проецировать свойства сущностей с помощью JPA и Hibernate.

Важно отметить, что Hibernate устарел свой API критериев начиная с версии 5.2 и далее в пользу API JPA CriteriaQuery . Но это только потому, что у команды Hibernate нет времени, чтобы синхронизировать два разных API, которые в значительной степени делают одно и то же.

И, конечно же, код, используемый в этой статье, можно найти на GitHub .