Запросы JPQL
find загружает одну сущность по id. Для всего остального — «все статьи больше чем с 50 просмотрами», «число статей» — есть JPQL (Jakarta Persistence Query Language). Он похож на SQL, но работает с сущностями и их полями, а не с таблицами и столбцами. Hibernate переводит запрос JPQL в SQL вашей базы. Расширенная версия языка у Hibernate называется HQL; любой запрос JPQL — заодно и корректный HQL.
Первый запрос
Примеры используют данные Seed из урока о ленивой загрузке: три статьи со 120, 80 и 45 просмотрами.
// src/main/java/com/example/blog/Main.java
Seed.run(emf);
emf.runInTransaction(em -> {
List<Post> popular = em.createQuery(
"select p from Post p where p.views > :min order by p.views desc", Post.class)
.setParameter("min", 50)
.getResultList();
popular.forEach(p -> IO.println(p.getTitle() + " (" + p.getViews() + ")"));
});Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where p1_0.views>? order by p1_0.views desc
Привет, Hibernate (120)
Типы record в Java 25 (80)| Часть | Значение |
|---|---|
select p from Post p | Post — имя класса-сущности, p — псевдоним |
p.views | Поле сущности, а не столбец |
:min | Именованный параметр; значение задаёт setParameter |
order by p.views desc | Сортировка, как в SQL |
Второй аргумент createQuery — тип результата. С ним вы получаете TypedQuery<Post> и List<Post> без приведений типов.
Одно значение
// src/main/java/com/example/blog/Main.java
Long total = em.createQuery("select count(p) from Post p", Long.class)
.getSingleResult();
IO.println("Всего статей: " + total);Hibernate: select count(p1_0.id) from Post p1_0
Всего статей: 3count возвращает Long. getSingleResult ожидает ровно одну строку.
Результат, которого может не быть
Если строки может не оказаться, используйте getSingleResultOrNull, появившийся в Jakarta Persistence 3.2:
// src/main/java/com/example/blog/Main.java
Post found = em.createQuery("select p from Post p where p.title = :title", Post.class)
.setParameter("title", "Ленивая загрузка")
.getSingleResultOrNull();
IO.println("Найдена: " + found.getTitle());
Post missing = em.createQuery("select p from Post p where p.title = :title", Post.class)
.setParameter("title", "Такой статьи нет")
.getSingleResultOrNull();
IO.println("Отсутствует: " + missing);Найдена: Ленивая загрузка
Отсутствует: nullЗачем параметры
Никогда не вклеивайте значения в текст запроса через +. Параметр передаётся в базу отдельно, поэтому заголовок вроде ' or 1=1 -- остаётся просто текстом и не может изменить запрос — это защита от SQL-инъекций, как PreparedStatement в JDBC.
Частая ошибка
В JPQL используются имена сущностей и полей, а не таблиц и столбцов. Если класс Post отображён на таблицу posts, запрос select p from posts p падает:
java.lang.IllegalArgumentException: org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'posts'То же бывает при опечатке в имени поля:
java.lang.IllegalArgumentException: org.hibernate.query.sqm.UnknownPathException: Could not resolve attribute 'titel' of 'com.example.blog.Post' [select p from Post p where p.titel = 'x']А getSingleResult без подходящей строки бросает jakarta.persistence.NoResultException: No result found for query [...] — для этого и нужен getSingleResultOrNull.