JOIN FETCH

Модуль 4. Работа с БД
14 уровень , 3 лекция
Открыта

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

Как мы уже сказали выше, у аннотации 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 лекция
Недоступна
task1404
Если ранее не подключал зависимости, то подключи их. Для этого используй Alt + Ctrl + Shift + S (в Идее), вкладка Libraries. Зависимости можно скачать здесь: https://javarush.com/downloads/ide/javarush/hibernate.zip Архив распакуй, и каждую зависимость добавь к модулю. Эта часть задания не проверяется, но если ее не выполнить, ты не сможешь локально выполнять код.
Комментарии (12)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Мая Уровень 82
3 ноября 2025
В этой лекции написано: "С помощью мы явно связываем сущности Task и Employee в нашем запросе." С помощью чего???
Роман Уровень 88
14 января 2025
хорошая лекция
Кирилл Уровень 111 Expert
14 ноября 2023
в примерах: 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

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 Уровень 41
30 июля 2024
t это алиас имени таблицы, а не колонки
Ярослав Уровень 111 Expert
5 сентября 2023
Задача не проходит валидацию
Max Dudin Уровень 6 Expert
14 октября 2023
тоже не проходит, даже с копипащенным решением...
jvatechs Уровень 111 Expert
17 октября 2023
Я даже написал в службу поддержки, на что они ответили: " Зепп Бранниган 7 октября, 19:52 Здравствуйте. Передали команде на проверку. Ожидайте, пожалуйста, обратной связи с понедельника." Уже идёт вторая неделя - но никакой обратной связи.
Олег Шукюров Уровень 41
11 января 2023
Ребят, объясните пожалуйста, зачем чтобы получить все таски мы джойним три таблицы, отбираем при этом все возможные не обязательно те которые есть у эмплои, т.е смысл джойна тут отпадает, нам просто нужны все таски и все, почему нельзя просто сделать так SELECT DISTINCT EmployeeTask.* FROM EmployeeTask ORDER BY EmployeeTask.deadline; Мы таким образом ведь получим все задачи??? Разложите пожалуйста по полочкам!
Станислав Future Уровень 39
27 апреля 2023
Если написать так, то мы получим таски с пустыми сотрудниками. Если потом решим для каждого таска вывести перебором имя сотрудника, то на каждое имя будет идти отдельный запрос в базу. Если же мы выгрузим таски с помощью джойна, то сотудники уже будут привязаны к таску, они запросятся и выгрузятся из базы сразу
Владимир Уровень 109 Expert
7 января 2023
task1404: не принимает решение без алиасов
Александр Огарков Уровень 4 Expert
4 января 2023
Тем кто не понял почему у JOIN FETCH есть предупреждение на запрос с setMaxResults() и setFirstResult(), советую прочитать статью, там же есть вариант решения этой проблемы