Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Соединения, проекции и массовые изменения

Запрос не обязан возвращать целые сущности. Часто нужно одно поле, число или короткая сводка для отчёта. В этом уроке JPQL переходит по связям, группирует строки и заполняет запись прямо из запроса. В конце — запросы, которые меняют сразу много строк.

Соединение через связь

В JPQL соединяют не по столбцам, а по полю связи:

// src/main/java/com/example/blog/Main.java
List<String> titles = em.createQuery(
                "select p.title from Post p join p.tags t where t.name = :tag order by p.id",
                String.class)
        .setParameter("tag", "hibernate")
        .getResultList();
IO.println("С тегом hibernate: " + titles);
Hibernate: select p1_0.title from Post p1_0 join post_tag t1_0 on p1_0.id=t1_0.post_id join Tag t1_1 on t1_1.id=t1_0.tag_id where t1_1.name=? order by p1_0.id
С тегом hibernate: [Привет, Hibernate, Ленивая загрузка]

Достаточно join p.tags t: о таблице post_tag Hibernate знает из отображения. Выборка p.title вместо p возвращает строки, поэтому тип результата — String.class. Обычный join не загружает связь в сущность — для этого есть join fetch из урока про N+1.

Сводка в запись

Создайте запись для результата:

// src/main/java/com/example/blog/AuthorStats.java
package com.example.blog;
 
public record AuthorStats(String name, long posts, long views) {
}

Конструкция select new вызывает её конструктор для каждой строки:

// src/main/java/com/example/blog/Main.java
List<AuthorStats> stats = em.createQuery("""
                select new com.example.blog.AuthorStats(a.name, count(p), sum(p.views))
                from Post p join p.author a
                group by a.name
                order by sum(p.views) desc
                """, AuthorStats.class)
        .getResultList();
stats.forEach(IO::println);
Hibernate: select a1_0.name,count(p1_0.id),sum(p1_0.views) from Post p1_0 join Author a1_0 on a1_0.id=p1_0.author_id group by a1_0.name order by sum(p1_0.views) desc
AuthorStats[name=Анна, posts=1, views=120]
AuthorStats[name=Олег, posts=1, views=80]
AuthorStats[name=Мария, posts=1, views=45]

После new пишется полное имя класса с пакетом. count и sum от целого поля возвращают Long, поэтому компоненты записи — long. Такие объекты не сущности: Hibernate их не отслеживает, и обходятся они дешевле загрузки целых статей.

ФункцияРезультат
count(p)Число строк
sum, avg, min, maxКак в SQL
size(p.comments)Размер коллекции
upper, lower, concat, lengthСтроковые функции

Изменение многих строк

Запросы update и delete меняют строки прямо в базе, не загружая сущности:

// src/main/java/com/example/blog/Bulk.java
emf.runInTransaction(em -> {
    Post post = em.find(Post.class, 1L);
 
    int updated = em.createQuery("update Post p set p.views = 0 where p.views < :limit")
            .setParameter("limit", 1000)
            .executeUpdate();
    IO.println("Обновлено строк: " + updated);
    IO.println("Просмотров в памяти: " + post.getViews());
 
    em.refresh(post);
    IO.println("Просмотров после refresh: " + post.getViews());
});
Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where p1_0.id=?
Hibernate: update Post p1_0 set views=0 where p1_0.views<?
Обновлено строк: 3
Просмотров в памяти: 120
Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views,c1_0.post_id,c1_0.id,c1_0.text from Post p1_0 left join Comment c1_0 on p1_0.id=c1_0.post_id where p1_0.id=?
Просмотров после refresh: 0

executeUpdate возвращает число изменённых строк. Такому запросу нужна активная транзакция.

Частая ошибка

Посмотрите на вывод ещё раз: после массового изменения статья, загруженная раньше, всё ещё показывает 120 просмотров. Массовые запросы идут в обход контекста персистентности, поэтому объекты, уже лежащие в памяти, сохраняют старые значения. Выполняйте массовые запросы до загрузки сущностей, обновляйте затронутые объекты через em.refresh или очищайте контекст через em.clear().

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.