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

Как мы уже сказали выше, у аннотации 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!
undefined
1
Задача
Модуль 4. Работа с БД, 14 уровень, 3 лекция
Недоступна
task1404
Если ранее не подключал зависимости, то подключи их. Для этого используй Alt + Ctrl + Shift + S (в Идее), вкладка Libraries. Зависимости можно скачать здесь: https://javarush.com/downloads/ide/javarush/hibernate.zip Архив распакуй, и каждую зависимость добавь к модулю. Эта часть задания не проверяется, но если ее не выполнить, ты не сможешь локально выполнять код.