Нативный SQL
JPQL покрывает большинство задач, но иногда нужен сам SQL: функция, которая есть только в вашей базе, оконная функция, вручную настроенный запрос для отчёта. Нативные запросы позволяют отправить любой SQL через тот же EntityManager, с параметрами и в текущей транзакции. Цена — переносимость: запрос пишется под конкретную базу данных.
Загрузка сущностей
Примеры используют данные Seed из урока о ленивой загрузке.
// src/main/java/com/example/blog/Main.java
List<Post> posts = em.createNativeQuery(
"select * from Post where views > :min order by views desc", Post.class)
.setParameter("min", 50)
.getResultList();
posts.forEach(p -> IO.println(p.getTitle()));Hibernate: select * from Post where views > ? order by views desc
Привет, Hibernate
Типы record в Java 25В запросе настоящие имена таблиц и столбцов: Post, views, author_id. Если вторым аргументом передать класс-сущность, Hibernate превратит каждую строку в управляемый объект Post. Именованные параметры вида :min здесь тоже работают. Метод возвращает «сырой» List, поэтому компилятор предупреждает о непроверенном преобразовании.
Строки массивами
Если результат — не сущность, просите Object[]: по массиву на строку.
// src/main/java/com/example/blog/Main.java
List<Object[]> rows = em.createNativeQuery("""
select a.name, count(p.id)
from Author a left join Post p on p.author_id = a.id
group by a.name
order by a.name
""", Object[].class)
.getResultList();
rows.forEach(r -> IO.println(Arrays.toString(r)));[Анна, 1]
[Мария, 1]
[Олег, 1]Типы элементов зависят от JDBC-драйвера: здесь имя — String, а количество — Long.
Строки в запись
Hibernate умеет и вызывать конструктор записи, компоненты которой соответствуют столбцам:
// src/main/java/com/example/blog/AuthorPosts.java
package com.example.blog;
public record AuthorPosts(String name, long posts) {
}// src/main/java/com/example/blog/Main.java
List<AuthorPosts> stats = em.createNativeQuery("""
select a.name, count(p.id)
from Author a left join Post p on p.author_id = a.id
group by a.name
order by a.name
""", AuthorPosts.class)
.getResultList();
stats.forEach(IO::println);AuthorPosts[name=Анна, posts=1]
AuthorPosts[name=Мария, posts=1]
AuthorPosts[name=Олег, posts=1]Столбцы сопоставляются по порядку: первый — с name, второй — с posts.
Что когда выбирать
| Задача | Инструмент |
|---|---|
| Обычная выборка и фильтрация | JPQL |
| Фильтры, собираемые во время работы | Criteria API |
| Возможности конкретной базы, сложные отчёты | Нативный SQL |
Нативные запросы, как и массовые JPQL-изменения, идут в обход контекста персистентности: update через SQL не меняет уже загруженные сущности.
Частая ошибка
Если нативный запрос загружает сущности, он должен вернуть все столбцы, которые сущность отображает. Если выбрать только часть:
// src/main/java/com/example/blog/Main.java
em.createNativeQuery("select id, title from Post where views > :min order by views desc", Post.class)Exception in thread "main" org.hibernate.exception.SQLGrammarException: Unable to find column position by name: author_id [Column "AUTHOR_ID" not found [42122-252]] [n/a]Чтобы загрузить часть столбцов, используйте Object[] или запись вместо класса-сущности.