Этап 6 - JPQL: запросы к entities вместо таблиц
JPQL делает запрос к entity model, а SQL делает запрос к database schema. Он похож на SQL, но работает с entity classes и entity fields, а не напрямую с table names и column names. SQL говорит select * from users; JPQL говорит SELECT u FROM User u. Hibernate переводит JPQL в SQL для конкретной базы данных.
Синтаксис JPQL
Базовый query выбирает из entity:
@Query("SELECT u FROM User u")
List<User> findAllUsers();
User - имя entity class, а u - alias. Conditions используют имена entity fields:
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
JPQL поддерживает WHERE, ORDER BY, GROUP BY, aggregate functions и joins через entity relationships. Join идет по object references, а не по raw foreign key columns:
@Query("SELECT o FROM Order o JOIN o.user u WHERE u.email = :email")
List<Order> findOrdersByUserEmail(@Param("email") String email);
Параметры
Named parameters используют :name и обычно понятнее positional parameters. Positional parameters используют ?1, ?2 и так далее. Named parameters лучше переживают refactoring, потому что смысл параметра виден прямо в query.
| Потребность query | Более удачный JPQL style | Почему это объясняет намерение |
|---|---|---|
| Отфильтровать по одному business value | WHERE u.email = :email | Имя параметра показывает, что значение является email, а не просто первым аргументом. |
| Отфильтровать по нескольким значениям | WHERE p.price BETWEEN :min AND :max | Named parameters не дают перепутать два значения одного Java type. |
| Сделать join через relationship | JOIN o.user u | JPQL идет по entity relation вместо ручного orders.user_id = users.id. |
| Вернуть list screen | SELECT new ...ProductSummary(...) | DTO projection грузит только поля, реально нужные response. |
DTO projection
Иногда API нужны только несколько полей. Загружать полные entities может быть расточительно, особенно если есть relationships. JPQL может создавать DTO projections:
@Query("""
SELECT new com.example.UserSummary(u.id, u.email, u.name)
FROM User u
WHERE u.active = true
ORDER BY u.name
""")
List<UserSummary> findActiveUserSummaries();
DTO projection полезен для read-only screens, list endpoints и reporting. Он уменьшает объем загружаемых данных и помогает избежать случайного lazy loading. Компромисс в том, что DTO constructor и query должны совпадать.
JPQL или derived queries
Spring Data умеет создавать queries по именам методов, например findByEmail или findByNameContainingIgnoreCase. Это отлично для простых условий. JPQL лучше, когда нужны joins, grouping, custom sorting logic, projections или условие, которое сделает имя метода нечитаемым.
Практика
Добавь queries для поиска пользователей по email, фильтрации товаров по price range и availability, а также сортировки товаров по name. Затем добавь DTO projection, который возвращает только product id, name и price. Сравни generated SQL через SQL logging. Цель не в заучивании JPQL syntax, а в понимании, что JPQL описывает операции над entity model.
Чек-лист понимания
- Могу объяснить, чем JPQL отличается от SQL.
- Знаю, что JPQL использует имена entities и fields.
- Могу использовать
@Queryс named parameters. - Понимаю, когда полезен DTO projection.
- Могу выбрать между derived query methods и JPQL.