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
. Следовательно, мы указали класс-кандидат как String
в методе createQuery()
.
Поскольку мы хотим проецировать одно свойство, мы использовали метод Query.select()
. Здесь идет речь о том, какое свойство нам нужно, поэтому в нашем случае мы будем использовать свойство name
из нашего объекта Product
.
Теперь давайте посмотрим на пример вывода, сгенерированный двумя вышеуказанными запросами:
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
нам придется действовать немного по-другому:
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
— вычисляет среднее значение для столбца в группе.max
— вычисляет максимальное значение для столбца в группеmin
— вычисляет минимальное значение для столбца в группе.наименьшее
— находит наименьшее из значений столбца (например, по алфавиту или по дате).sum
— вычисляет сумму значений столбца в группе
4. Спящие проекции
В отличие от 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
, который оборачивает другие проекции , позволяя выбирать несколько значений .
Мы можем создать ProjectionList ,
используя метод Projections.projectionList()
, например , показать идентификатор
и имя
продукта
:
Criteria criteria = session.createCriteria(Product.class);
criteria = criteria.setProjection(
Projections.projectionList()
.add(Projections.id())
.add(Projections.property("name")));
4.3. Проецирование агрегатных функций
Как и CriteriaBuilder
, класс Projections
также предоставляет методы для агрегатных функций.
Давайте посмотрим, как мы можем реализовать пример count, который мы видели ранее:
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 является использование псевдонима для проекции.
Это особенно полезно при использовании агрегатной функции, так как мы можем ссылаться на псевдоним в экземплярах 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 отказался от своего Criteria API, начиная с версии 5.2, в пользу JPA CriteriaQuery API . Но это только потому, что у команды Hibernate нет времени на синхронизацию двух разных API, которые в значительной степени делают одно и то же.
И, конечно же, код, использованный в этой статье, можно найти на GitHub .