«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 мы можем сделать это, включив только имя в предложение select:

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

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

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

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

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

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

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

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

3.2. Многостолбцовые проекции

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

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

Но при использовании CriteriaBuilder нам придется делать вещи, немного иначе:

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<Product> product = query.from(Product.class);
query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice"));
List<Object[]> 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() в JPQL:

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

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

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

Здесь мы использовали метод count() CriteriaBuilder.

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

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

Помимо count(), CriteriaBuilder предоставляет ряд других агрегатных функций:

    avg — вычисляет среднее значение для столбца в a group max — вычисляет максимальное значение для столбца в группе min — вычисляет минимальное значение для столбца в группе less — находит наименьшее значение столбца (например, по алфавиту или по дате). sum — вычисляет сумму значений столбца в группе

4. Hibernate Projections

В отличие от JPA, Hibernate предоставляет org.hibernate.criterion.Projection для проецирования с помощью запроса Criteria. Он также предоставляет класс org.hibernate.criterion.Projections, фабрику для экземпляров Projection.

4.1. Проекции с одним столбцом

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

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

«

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

4.2. Многостолбцовые проекции

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

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

Мы можем создать ProjectionList, используя метод Projections.projectionList(), например, показать идентификатор и имя продукта:

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

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

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

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

Важно отметить, что мы не указали GROUP BY напрямую в объекте Criteria. Вызов groupProperty запускает это для нас.

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

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

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

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"));

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

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

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

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