Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье мы рассмотрим, как лучше всего написать JPQL-запрос для проекции, опустив имя пакета при использовании JPA, Hibernate и Spring.
Как я уже объяснял, для прогнозов это наиболее эффективный способ извлечения данных при использовании JPA и Hibernate.
Как написать запрос compact TO projection с помощью JPA @vlad_mihalcea https://t.co/EDgXuEbsFX pic.twitter.com/rAf1VZgtAA
Модель предметной области
Давайте рассмотрим следующую сущность Post
и связанный с ней объект Позиция
Значение.
Мы видим, что сущность Post
имеет семь атрибутов, в то время как PostDTO
имеет только два из них. Если для вашего бизнес-варианта использования требуются только два атрибута , содержащиеся в PostDTO
, то будет более эффективно получить проекцию PostDTO
, а не список Post
сущностей.
Запрос проекции DTO с помощью JPA
Спецификация JPA определяет выражение конструктора для передачи полного имени класса, которое будет использоваться в качестве заполнителя для выбранных атрибутов сущности:
ListpostDTOs = entityManager.createQuery(""" select new com.vladmihalcea.book.hpjp.hibernate.forum.dto.PostDTO( p.id, p.title ) from Post p where p.createdOn > :fromTimestamp """, PostDTO.class) .setParameter( "fromTimestamp", LocalDate.of(2016, 1, 1).atStartOfDay() ) .getResultList();
Согласно стандарту JPA, выражение конструктора DTO должно принимать полное имя класса Java, представляющего объект DTO, в котором мы хотим хранить выбранные атрибуты сущности.
Но это совсем не приятно!
Я бы предпочел использовать простое имя класса или, по крайней мере, короткое имя папки, если есть два DTO с одинаковым именем, но с разными структурами.
Более простой запрос проекции DTO с JPA и гибернацией
Итак, в принципе, вот как я хочу писать В проекции:
ListpostDTOs = entityManager.createQuery(""" select new PostDTO( p.id, p.title ) from Post p where p.createdOn > :fromTimestamp """, PostDTO.class) .setParameter( "fromTimestamp", LocalDate.of(2016, 1, 1).atStartOfDay() ) .getResultList();
В принципе, я хочу иметь возможность использовать простое имя класса Java по умолчанию, вместо того, чтобы указывать полное имя класса для каждого выражения конструктора JPA.
Чтобы иметь возможность использовать простое имя класса Java, нам необходимо использовать Интегратор импорта классов
утилиту, предоставляемую проектом Hibernate Types :
Декларативная конфигурация
Если вы используете декларативную конфигурацию, то сначала вам необходимо создать класс , реализующий Hibernate IntegratorProvider
, который возвращает настроенный ClassImportIntegrator
экземпляр:
public class ClassImportIntegratorIntegratorProvider implements IntegratorProvider { @Override public ListgetIntegrators() { return List.of( new ClassImportIntegrator( List.of( PostDTO.class ) ) ); } }
После этого вам необходимо установить для свойства конфигурации hibernate.integrator_provider
полное имя ClassImportIntegratorIntegratorProvider
.
Если вы используете Spring Boot, вы можете объявить свойство hibernate.integrator_provider
в файле конфигурации application.properties
следующим образом:
spring.jpa.properties.hibernate.integrator_provider=com.vladmihalcea.book.hpjp.ClassImportIntegratorIntegratorProvider
Если вы являетесь Java EE, вы можете установить свойство hibernate.integrator_provider
в persistence.xml
Файл конфигурации JPA, например:
Вот и все!
Программная конфигурация
Вы также можете настроить свойство hibernate.integrator_provider
программно, используя конфигурацию Spring на основе Java, либо с помощью JPA, либо с помощью собственных стратегий начальной загрузки API Hibernate.
Весна и JPA
Чтобы загрузить JPA с Spring, вам необходимо использовать LocalContainerEntityManagerFactoryBean
:
@Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); emf.setPersistenceUnitName(getClass().getSimpleName()); emf.setPersistenceProvider(new HibernatePersistenceProvider()); emf.setDataSource(dataSource()); emf.setPackagesToScan(packagesToScan()); emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); Properties properties = new Properties(); properties.setProperty( "hibernate.dialect", hibernateDialect ); properties.put( "hibernate.integrator_provider", (IntegratorProvider) () -> Collections.singletonList( new ClassImportIntegrator( List.of( PostDTO.class ) ) ) ); emf.setJpaProperties(properties); return emf; }
Обратите внимание, как мы передали свойство hibernate.integrator_provider
конфигурации в LocalContainerEntityManagerFactoryBean
через его setJpaProperties
метод.
Весна и зимняя спячка
Чтобы запустить собственный режим гибернации с помощью Spring, вам необходимо использовать компонент Sessionfactory
:
@Bean public LocalSessionFactoryBean sessionFactory() { LocalSessionFactoryBean sf = new LocalSessionFactoryBean(); sf.setDataSource(dataSource()); sf.setPackagesToScan(packagesToScan()); Properties properties = new Properties(); properties.setProperty( "hibernate.dialect", hibernateDialect ); sf.setHibernateProperties(properties); sf.setHibernateIntegrators( new ClassImportIntegrator( List.of( PostDTO.class ) ) ); return sf; }
Использование относительных имен пакетов
По умолчанию интегратор импорта классов
зарегистрирует предоставленные DTO, используя их простое имя класса. Однако, если у вас есть несколько DTO с одинаковым именем, расположенных в разных пакетах, вам необходимо зарегистрировать относительное имя пакета, чтобы различать различные классы DTO.
Полное имя класса PostDTO
: com.vladmihalcea.book.pop.hibernate.forum.to.Post D TO
. Таким образом, мы можем настроить интегратор импорта классов
для исключения пути com.vladmihalcea.book.pop.hibernate
, чтобы мы могли ссылаться на PostDTO
, используя оставшийся относительный путь, forum.dto.PostDTO
.
Чтобы исключить префикс пакета, вам необходимо вызвать метод exclude Path
следующим образом:
ListpostDTOs = entityManager.createQuery(""" select new forum.dto.PostDTO( p.id, p.title ) from Post p where p.createdOn > :fromTimestamp """, PostDTO.class) .setParameter( "fromTimestamp", LocalDate.of(2016, 1, 1).atStartOfDay() ) .getResultList();
Круто, правда?
Если вам понравилась эта статья, держу пари, вам также понравятся моя Книга и Видеокурсы.
Вывод
Опущение имени пакета DTO в запросе JPA, безусловно, является тем типом улучшения, о котором большинство разработчиков Java мечтали в течение длительного времени, о чем свидетельствуют положительные реакции, которые я получил на этот твит.
Наслаждайтесь запуском более простых для проецирования запросов с помощью JPA и гибернации.