Gymterview
middle

HQL и JPQL — что это и чем отличаются от SQL?

JPQL (Jakarta Persistence Query Language) — объектно-ориентированный язык запросов, определённый спецификацией JPA. Работает с сущностями и их полями, а не с таблицами и столбцами. HQL (Hibernate Query Language) — расширение JPQL от Hibernate с дополнительными возможностями.

JPQL vs SQL

Пример
-- SQL: работает с таблицами
SELECT u.user_name, d.dept_name
FROM users u
JOIN departments d ON u.department_id = d.id
WHERE u.status = 'ACTIVE';

-- JPQL: работает с сущностями
SELECT u.name, u.department.name
FROM User u
WHERE u.status = com.example.UserStatus.ACTIVE

Основные возможности JPQL

Примеры запросов
// SELECT
List<User> users = em.createQuery(
    "SELECT u FROM User u WHERE u.name LIKE :name", User.class)
    .setParameter("name", "%John%")
    .getResultList();

// JOIN
List<User> users = em.createQuery(
    "SELECT u FROM User u JOIN u.department d WHERE d.name = :deptName", User.class)
    .setParameter("deptName", "Engineering")
    .getResultList();

// Агрегация
Long count = em.createQuery(
    "SELECT COUNT(u) FROM User u WHERE u.status = :status", Long.class)
    .setParameter("status", UserStatus.ACTIVE)
    .getSingleResult();

// DTO-проекция (конструктор)
List<UserDto> dtos = em.createQuery(
    "SELECT new com.example.UserDto(u.name, u.email) FROM User u", UserDto.class)
    .getResultList();

// UPDATE (bulk)
int updated = em.createQuery(
    "UPDATE User u SET u.status = :newStatus WHERE u.lastLogin < :date")
    .setParameter("newStatus", UserStatus.INACTIVE)
    .setParameter("date", cutoffDate)
    .executeUpdate();

// DELETE (bulk)
int deleted = em.createQuery(
    "DELETE FROM User u WHERE u.status = :status")
    .setParameter("status", UserStatus.DELETED)
    .executeUpdate();

Важное

  • JPQL — стандарт JPA, HQL — расширение Hibernate; JPQL-код переносим между реализациями
  • JPQL работает с сущностями, не таблицами: FROM User u, не FROM users u
  • Навигация по связям через точку: u.department.name → Hibernate сгенерирует JOIN
  • Bulk UPDATE/DELETE обходят Persistence Context и L1/L2 Cache — после них нужен em.clear()

Частые ошибки

  • Конкатенация параметров в запросе — SQL Injection! Всегда используйте setParameter()
  • Bulk UPDATE без clear() — L1 Cache не знает об изменениях, данные в памяти устарели
  • Навигация по LAZY-связям в SELECT — SELECT u.department.name FROM User u генерирует JOIN, но если department LAZY и не загружен, может привести к N+1 в других сценариях
  • Забывать про fetch — SELECT u FROM User u не загружает LAZY-связи; для них нужен JOIN FETCH

Как используется в 2026

  • JPQL/HQL — основной инструмент для нетривиальных запросов в JPA-приложениях
  • Spring Data @Query использует JPQL по умолчанию, native SQL — через nativeQuery = true
  • Для type-safe запросов — Criteria API или QueryDSL
  • HQL в Hibernate 6 получил новые функции: ARRAY, JSON, улучшенный синтаксис

На собеседовании: покажите разницу между SQL и JPQL: SQL работает с таблицами, JPQL — с сущностями. Упомяните навигацию через точку (u.department.name). Если спросят про безопасность — всегда setParameter(), никогда конкатенация. Про bulk-операции: они обходят Persistence Context, нужен clear().