TGViewer
Spring АйО Spring АйО @spring_aio · 11K subscribers
Post #136 3.16K
✨ Spring Tips: Рекурсивные запросы в Hibernate

Рекурсивные запросы в SQL очень полезны при работе с иерархическими или графовыми структурами данных. Конструкция WITH, введенная в SQL:1999, позволяет задавать Common Table Expressions (CTE), которые представляют собой именованные подзапросы. CTE упрощают сложные запросы, улучшают их читаемость и, что самое важное, позволяют нам реализовать рекурсию.

Рассмотрим таблицу, которая хранит информацию о сотрудниках и их менеджерах:


CREATE TABLE employees (
employee_id INTEGER GENERATED BY DEFAULT AS IDENTITY NOT NULL,
employee_name VARCHAR(255),
manager_id INTEGER,
CONSTRAINT pk_employees PRIMARY KEY (employee_id)
);

ALTER TABLE employees ADD CONSTRAINT
FK_EMPLOYEES_ON_MANAGER FOREIGN KEY (manager_id)
REFERENCES employees (employee_id);


Эта таблица представляет собой список сотрудников, где у каждого сотрудника есть уникальный идентификатор (employee_id), имя (employee_name) и ссылка на менеджера (manager_id). Поле manager_id является внешним ключом, ссылающимся на employee_id в той же таблице, что позволяет создать иерархическую структуру, где один сотрудник может быть менеджером для других.

Предположим, что нам нужно выбрать всех подчиненных определенного менеджера. Для решения этой задачи мы можем использовать рекурсивный SQL запрос:


WITH RECURSIVE EmployeeHierarchy AS (
-- Базовый случай: начнем с верхнего уровня менеджера
SELECT employee_id, employee_name, manager_id
FROM employees
WHERE manager_id = :id

UNION ALL

-- Рекурсивный случай: найдем всех сотрудников, подчиненных найденным ранее сотрудникам
SELECT e.employee_id, e.employee_name, e.manager_id
FROM employees e
INNER JOIN EmployeeHierarchy eh ON e.manager_id = eh.employee_id
)

SELECT *
FROM EmployeeHierarchy;


До выхода версии Hibernate 6.2, для выполнения подобных запросов приходилось использовать nativeQuery. Пример с использованием entityManager выглядит следующим образом:


var sql = {SQL-запрос, представленный выше};

var employees = entityManager.createNativeQuery(sql, Employee.class)
.setParameter("id", 1)
.getResultList();


Либо так, если используется Spring Data JPA:


public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
@Query(value = "{SQL-запрос, представленный выше}", nativeQuery = true)
List<Employee> findAllEmployeesByManagerIdSQL(@Param("id") Integer id);
}

List<Employee> employees = employeeRepository.findAllEmployeesByManagerIdSQL(1);


С выходом Hibernate 6.2, появилась возможность писать рекурсивные запросы, используя HQL. Запрос, представленный выше, теперь можно написать так:


String jpql = """
WITH EmployeeHierarchy AS (
SELECT e.employeeId AS id, e.employeeName AS name, e.manager AS mgr
FROM Employee e
WHERE e.manager.id = :id

UNION ALL

SELECT e.employeeId, e.employeeName, e.manager.id
FROM Employee e
JOIN EmployeeHierarchy eh ON e.manager.id = eh.id
)
SELECT new Employee(
eh.id,
eh.name,
eh.mgr
)
FROM EmployeeHierarchy eh
""";

var employees = entityManager.createQuery(jpql, Employee.class)
.setParameter("id", 1)
.getResultList();


А также со Spring Data JPA:


public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
@Query(value = "{JPQL-запрос, представленный выше}")
List<Employee> findAllEmployeesByManagerIdJPQL(@Param("id") Integer id);
}

List<Employee> employees = employeeRepository.findAllEmployeesByManagerIdJPQL(1);


Как можно заметить, синтаксис HQL и SQL запросов довольно сильно похож. Но есть некоторые различия:

* В отличие от стандартного SQL, в Hibernate нет необходимости использовать ключевое слово RECURSIVE
* В Hibernate имена атрибутов CTE задаются через псевдонимы в выражении SELECT. Другими словами, в заголовке CTE имена не указываются.

#SpringTips #Hiberante #CTE
  • 👍 29
  • ❤ 5
  • 🔥 4
More from @spring_aio
  1. Oct 16, 2026😀 Joker 2026 && Spring АйО 🍃 Два дня в Петербурге пролетели быстрее, чем прогрев JVM. До…
  2. Oct 11, 2026🎬 Записи докладов с JPoint 2026 уже в открытом доступе JUG Ru Group выложили записи с про…
  3. Oct 9, 2026👩‍💻 Kotlin получил почти настоящие статики Начиная с Kotlin 2.5.0-Beta1 можно эксперимен…
  4. Oct 8, 2026🍃 Spring Boot ломает совместимость, G1 тормозит микросервисы, Java долго релизит фичи | S…
  5. Oct 6, 2026👩‍💻 Spring Config Import без магии У наших друзей из Axelix вышла статья о том, как Spri…
  6. Oct 5, 2026🥲 В Java 27 TLS научился переживать квантовую атаку При установке HTTPS-соединения клиент…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →