HibernateMiddleSystem design

Что такое @NamedQuery и @NamedNativeQuery в Hibernate?

@NamedQuery задаёт JPQL-запрос с именем на уровне сущности, который проверяется при старте приложения. @NamedNativeQuery — то же самое, но для нативного SQL. Оба кэшируются в SessionFactory и вызываются через em.createNamedQuery().

@NamedQuery

Аннотация @NamedQuery позволяет определить JPQL-запрос один раз на уровне класса сущности и переиспользовать его по имени. Ключевое преимущество — запрос парсится и валидируется при инициализации SessionFactory, а не при первом вызове. Синтаксическая ошибка в JPQL обнаруживается на старте приложения.

@Entity
@NamedQueries({
    @NamedQuery(
        name = "User.findByEmail",
        query = "SELECT u FROM User u WHERE u.email = :email",
        hints = @QueryHint(name = "org.hibernate.cacheable", value = "true")
    ),
    @NamedQuery(
        name = "User.findActiveByRole",
        query = "SELECT u FROM User u WHERE u.active = true AND u.role = :role ORDER BY u.createdAt DESC"
    )
})
public class User {
    @Id @GeneratedValue
    private Long id;
    private String email;
    private String role;
    private boolean active;
    private LocalDateTime createdAt;
}

Вызов через JPA EntityManager:

TypedQuery<User> query = em.createNamedQuery("User.findByEmail", User.class);
query.setParameter("email", "alice@example.com");
User user = query.getSingleResult();

Соглашение об именовании: EntityName.queryDescription — это конвенция Spring Data JPA, которая позволяет репозиторию автоматически находить named query по имени метода.

// Spring Data JPA автоматически связывает метод с @NamedQuery "User.findByEmail"
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email); // ищет @NamedQuery "User.findByEmail"
}

@NamedNativeQuery

Используется для нативных SQL-запросов, когда JPQL недостаточен — например, при использовании специфичных для БД функций, оконных функций, RETURNING, ON CONFLICT и т.п. Нативные запросы не проверяются при старте — ошибка проявится только при выполнении.

@Entity
@NamedNativeQuery(
    name = "User.findTopByActivity",
    query = "SELECT u.id, u.email, COUNT(l.id) as login_count " +
            "FROM users u " +
            "JOIN login_events l ON l.user_id = u.id " +
            "WHERE l.created_at > NOW() - INTERVAL '30 days' " +
            "GROUP BY u.id, u.email " +
            "ORDER BY login_count DESC " +
            "LIMIT :limit",
    resultSetMapping = "UserActivityMapping"
)
@SqlResultSetMapping(
    name = "UserActivityMapping",
    columns = {
        @ColumnResult(name = "id", type = Long.class),
        @ColumnResult(name = "email", type = String.class),
        @ColumnResult(name = "login_count", type = Long.class)
    }
)
public class User { /* ... */ }

Вызов:

Query query = em.createNamedQuery("User.findTopByActivity");
query.setParameter("limit", 10);
List<Object[]> results = query.getResultList();

Если результат должен маппиться на сущность целиком, достаточно указать resultClass:

@NamedNativeQuery(
    name = "User.findByCountry",
    query = "SELECT * FROM users WHERE country_code = :code",
    resultClass = User.class
)

Named Queries в отдельном XML-файле

Для разделения запросов и кода их можно вынести в orm.xml:

<named-query name="User.findByEmail">
  <query>SELECT u FROM User u WHERE u.email = :email</query>
</named-query>

Подводные камни

  • @NamedQuery валидируется при старте только если Hibernate может построить метамодель — с некоторыми custom типами валидация может быть неполной.
  • @NamedNativeQuery не валидируется при старте и не переносится автоматически между СУБД из-за диалектных отличий SQL.
  • Дублирование имён в @NamedQuery вызывает HibernateException при старте — имена должны быть уникальны в рамках persistence unit.
  • @SqlResultSetMapping для @NamedNativeQuery с DTO-проекциями требует точного совпадения имён столбцов с псевдонимами в SQL — регистр важен.
  • Spring Data JPA ищет @NamedQuery по паттерну ClassName.methodName — несоответствие имени ведёт к тихому игнорированию и генерации запроса из имени метода.
  • Named Queries не поддерживают динамические условия — для фильтрации с опциональными параметрами лучше использовать Criteria API или Spring Data Specifications.
  • Кэширование результатов через org.hibernate.cacheable=true требует включённого query cache, иначе hint молча игнорируется.

Common mistakes

  • Путать термин «named queries» с соседним механизмом Hibernate.
  • Не называть границу lifecycle, transaction, thread или request для «named queries».
  • Игнорировать production-эффекты «named queries»: latency, SQL shape, memory, security или observability.

What the interviewer is testing

  • Попросить объяснить механизм «named queries» на минимальном примере.
  • Проверить, видит ли кандидат failure mode и диагностику для «named queries».
  • Уточнить, какие настройки или API меняют «named queries» в реальном сервисе.

Sources

Related topics