Создадим именованный запрос в файле package-info.java, который в дальнейшем будет вызываться по строке с именем. Именованные запросы, вызываемые через методы репозитория, рассмотрены в другой статье.
Если у нас есть некоторый пакет со связанными друг с другом сущностями, например, пакет client с классами-сущностями Client, Address, BillingData и т.п., то может так случится, что у нас будет для этих сущностей некий набор именованных запросов, который было бы удобно хранить в одном месте. Таким местом может быть файл package-info.java, который можно создать в любом java-пакете и поместить в него через аннотации ту или иную метаинформацию о пакете. В частности Hibernate позволяет разместить здесь аннотации, описывающие именованные запросы.
Создадим минимальное Spring Boot приложение с поддержкой JPA/Hibernate.
Добавим в проект следующую сущность:
1 2 3 4 5 6 7 8 9 |
@Entity public class Person { @Id @GeneratedValue private Long id; private String name; private Integer age; //Конструкторы, геттеры и сеттеры, toString() и т.д. } |
В пакете, где мы расположили файл Person.java, также создадим файл package-info.java со следующим содержимым:
1 2 3 4 5 6 7 8 |
@org.hibernate.annotations.NamedQueries({ @org.hibernate.annotations.NamedQuery( name = "findOlder", query = "select p from Person p where p.age >= ?1" ) }) package com.example.demo.domain; |
Как видите директива package com.example.demo.domain; аннотирована аннотацией @NamedQueries. Поскольку это чисто хибернейтовская аннотация, а не стандартная JPA аннотация, то по правилам приличия пишется её полное имя.
В аннотацию @NamedQueries передаётся массив аннотаций @NamedQuery, каждая из которых содержит два поля:
- name — имя запроса
- query — собственно код запроса
Имя запроса является произвольной строкой. Далее указанный запрос будет вызываться именно через эту строку.
Создадим репозиторий для сущности Person. Ничего своего добавлять в репозиторий мы не будем. Нам достаточно стандартных методов:
1 2 3 |
@Repository public interface PersonRepository extends JpaRepository<Person, Long> { } |
Далее на примере тестового метода, рассмотрим, как вызвать именованный запрос по строке с его именем. Создадим следующий тестовый класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@SpringBootTest public class GlobalMetaDataPackageTest { @Autowired PersonRepository personRepository; @PersistenceContext private EntityManager entityManager; @BeforeEach void cleanTable() { personRepository.deleteAll(); } @Test void globalMetaDataNamedQueryViaString() { Person irina = new Person("Irina", 29); Person vladimir = new Person("Vladimir", 30); personRepository.save(irina); personRepository.save(vladimir); Query findOlderQuery = entityManager.createNamedQuery("findOlder"); findOlderQuery.setParameter(1, 30); List<Person> fetchResult = findOlderQuery.getResultList(); assertEquals(fetchResult.size(), 1); assertEquals(fetchResult.get(0), vladimir); } } |
Обратите внимание, что бин PersonRepository автоварится только для того, чтобы сохранить первичные данные в базу в рамках теста. Для запуска именованного запроса он не требуется.
Основная смысловая нагрузка ложится на следующий участок кода:
1 2 3 4 5 6 |
@PersistenceContext private EntityManager entityManager ... Query findOlderQuery = entityManager.createNamedQuery("findOlder"); findOlderQuery.setParameter(1, 30); List<Person> fetchResult = findOlderQuery.getResultList(); |
Сначала с помощью аннотации @PersistenceContext мы получаем доступ к бину EntityManager, из которого можно извлечь именованный запрос по строке с собственно именем. Затем в этот запрос мы можем передать параметр с помощью метода setParameter(). Затем вызовом метода getResultList() мы отправляем запрос на выполнение.
Основным недостатком данного способа является необходимость использовать простую строку с именем запроса. Такую строку, конечно, желательно вынести в отдельную константу, либо вообще использоваться вызовом именованного запроса через метод репозитория.