Рубрики
Без рубрики

Как написать компактный запрос проекции DTO с помощью JPA

Узнайте, как написать лучший для проекции JPQL-запрос, опустив имя пакета при использовании JPA, Hibernate и Spring.

Автор оригинала: 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 определяет выражение конструктора для передачи полного имени класса, которое будет использоваться в качестве заполнителя для выбранных атрибутов сущности:

List postDTOs = 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 и гибернацией

Итак, в принципе, вот как я хочу писать В проекции:

List postDTOs = 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 List getIntegrators() {
        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 следующим образом:

List postDTOs = 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 и гибернации.