JOIN FETCH

Открыта

Описание проблемы

Как мы уже сказали выше, у аннотации LazyCollectionOption.EXTRA есть проблема — она выполняет по отдельному запросу к базе для каждого объекта. Нужно как-то объяснить Hibernate, что мы хотим, чтобы он сразу загрузил все дочерние объекты для наших родительских объектов.

Разработчики Hibernate предложили решение этой проблемы — оператор join fetch в HQL.

Пример HQL-запроса:

select distinct task from Task t left join fetch t.employee order by t.deadline

В этом запросе все просто и сложно одновременно. Давай попробуем сконструировать его по частям.

Вариант 1

Мы хотим загрузить все объекты Task, отсортированные по deadline. Вот как будет выглядеть этот запрос:

select task from Task t order by t.deadline

Пока все понятно. Но поле employee класса Task будет содержать коллекцию сотрудников Employee, которая помечена аннотацией EXTRA. И объекты этой коллекции загружены не будут.

Вариант 2

Принудительно заставляем Hibernate загрузить дочерние объекты для объекта Task.

select task from Task t join fetch t.employee order by t.deadline

С помощью мы явно связываем сущности Task и Employee в нашем запросе. Hibernate об этом и так знает, так как мы используем аннотации @ManyToMany для этих полей.

Но нам нужен оператор join, чтобы дополнить его оператором fetch и получить join fetch. Именно таким способом мы указываем Hibernate, что объекты в коллекциях Task.employee нужно загрузить из базы при выполнении нашего запроса.

Вариант 3

У предыдущего решения есть несколько пробочек. Во-первых, после использования join SQL не вернет нам объекты Task, у которых нет связанных с ними объектов в таблице Employee. Именно так работает inner join.

Поэтому нам нужно дополнить наш join оператором left и превратить его в left join. Пример:

select task from Task t left join fetch t.employee order by t.deadline

Вариант 4

Но и это еще не все. Если в твоем коде отношение между сущностями many-to-may, то возникнут дубликаты в результатах запроса. Один и тот же объект task может быть найден у разных сотрудников (объектов Employee).

Поэтому тебе нужно добавить ключевое слово distinct после слова select, чтобы избавиться от дубликатов объекта Task.

select distinct task from Task t left join fetch t.employee order by t.deadline

Именно так в 4 шага мы пришли к тому запросу, с которого начали. Ну а Java-код будет выглядеть вполне ожидаемо:

String hql = " select distinct task from Task t left join fetch t.employee order by t.deadline";
Query<Task> query = session.createQuery( hql, Task.class);
return query.list();

Ограничения JOIN FETCH

Никто не идеален. Оператор JOIN FETCH тоже. Он имеет достаточно много ограничений. И первое из них — это использование методов setMaxResults() и setFirstResult().

Для оператора JOIN FETCH наш Hibernate сгенерирует очень сложный запрос, в котором объединить в одну три таблицы: employee, task и employee_task. Фактически это запрос не сотрудников или задач, а всех известных пар сотрудник-задача.

И SQL может применить свои операторы LIMIT и OFFSET именно к этому запросу пар сотрудник-задача. В то же время из HQL запроса явно следует, что мы хотим получить именно задачи (Task), и если мы переделим свои параметры FirstResult и MaxResult, то они должны относиться именно к объектам Task.

Если ты напишешь такой код:

String hql = " select distinct task from Task t left join fetch t.employee order by t.deadline";
Query<Task> query = session.createQuery( hql, Task.class);
       	    query.setFirstResult(0);
        	   query.setMaxResults(1);
return query.list();

То Hibernate не сможет правильно преобразовать FirstResult и MaxResult в параметры OFFSET и LIMIT у SQL-запроса.

Вместо этого он сделает три вещи:

  • SQL-запрос выберет вообще все данные из таблицы и вернет их Hibernate
  • Hibernate сам у себя в памяти отберет нужные записи и вернет их тебе
  • Hibernate выдаст предупреждение

Предупреждение будет чем-то типа такого:

WARN [org.hibernate.hql.internal.ast.QueryTranslatorImpl] HHH000104: 
firstResult/maxResults specified with collection fetch; applying in memory!
1
Задача
Модуль 4. Работа с БД,  14 уровень3 лекция
Недоступна
JOIN FETCH
JOIN FETCH
Комментарии (13)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Мая
Уровень 82
3 ноября 2025, 18:04
В этой лекции написано: "С помощью мы явно связываем сущности Task и Employee в нашем запросе." С помощью чего???
Anonymous #3613348
Уровень 100
4 июня, 13:15
"и если мы переделим свои параметры" Переделаем?
Роман
Уровень 88
14 января 2025, 14:48
хорошая лекция
Кирилл
Уровень 111
Expert
14 ноября 2023, 14:08
в примерах: select distinct task from Task t left join fetch t.employee а надо: select distinct t from Task t left join fetch t.employees тогда и задача пройдет тестирование
Max Dudin
Уровень 6
Expert
14 октября 2023, 16:07
String hql = " select distinct task from Task t left join fetch t.employee order by t.deadline";
раз мы уже применили алиас почему не String hql = " select distinct t from Task t left join fetch t.employee order by t.deadline"; ?
Gans Electro
Уровень 36
30 июля 2024, 13:51
t это алиас имени таблицы, а не колонки
Ярослав
Уровень 111
Expert
5 сентября 2023, 21:11
Задача не проходит валидацию
Max Dudin
Уровень 6
Expert
14 октября 2023, 16:13
тоже не проходит, даже с копипащенным решением...
jvatechsBackend Developer в Ищу стажировкуExpert
17 октября 2023, 12:02
Я даже написал в службу поддержки, на что они ответили: " Зепп Бранниган 7 октября, 19:52 Здравствуйте. Передали команде на проверку. Ожидайте, пожалуйста, обратной связи с понедельника." Уже идёт вторая неделя - но никакой обратной связи.
Олег ШукюровJava Developer в Madela
11 января 2023, 01:57
Ребят, объясните пожалуйста, зачем чтобы получить все таски мы джойним три таблицы, отбираем при этом все возможные не обязательно те которые есть у эмплои, т.е смысл джойна тут отпадает, нам просто нужны все таски и все, почему нельзя просто сделать так SELECT DISTINCT EmployeeTask.* FROM EmployeeTask ORDER BY EmployeeTask.deadline; Мы таким образом ведь получим все задачи??? Разложите пожалуйста по полочкам!
Станислав Future
Уровень 39
27 апреля 2023, 09:29
Если написать так, то мы получим таски с пустыми сотрудниками. Если потом решим для каждого таска вывести перебором имя сотрудника, то на каждое имя будет идти отдельный запрос в базу. Если же мы выгрузим таски с помощью джойна, то сотудники уже будут привязаны к таску, они запросятся и выгрузятся из базы сразу
Владимир
Уровень 109
Expert
7 января 2023, 15:31
task1404: не принимает решение без алиасов
Александр Огарков
Уровень 5
Expert
4 января 2023, 10:37
Тем кто не понял почему у JOIN FETCH есть предупреждение на запрос с setMaxResults() и setFirstResult(), советую прочитать статью, там же есть вариант решения этой проблемы