How to write a compact DTO projection query with JPA

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!

Introduction

In this article, we are going to see how we can write the best DTO projection JPQL query by omitting the package name when using JPA, Hibernate, and Spring.

As I already explained, DTO projections are the most efficient way of fetching data when using JPA and Hibernate.

Domain Model

Let’s consider the following Post entity and its associated PostDTO Value Object.

Import DTO projection in JPA query

We can see that the Post entity has seven attributes, while PostDTO has only two of those. If our business use case requires just the two attributes contained in PostDTO, then it’s going to be more efficient to fetch a PostDTO projection rather than a list of Post entities.

DTO projection query with JPA

The JPA specification defines the constructor expression for passing a fully-qualified DTO class name to be used as a placeholder for the selected entity attributes:

List<PostDTO> 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",
    Timestamp.from(
        LocalDate.of(2020, 1, 1)
            .atStartOfDay()
            .toInstant(ZoneOffset.UTC)
    )
)
.getResultList();

According to the JPA standard, the DTO constructor expression must take the fully-qualified name of the Java class representing the DTO object we want to hold the selected entity attributes.

But this is not nice at all!

I’d rather want to use the simple class name or, at least, a short folder name if there are two DTOs with the same name, but with different structures.

A simpler DTO projection query with JPA and Hibernate

So, basically, this is how I want to write DTO projections:

List<PostDTO> postDTOs = entityManager.createQuery(
    "select new PostDTO(" +
    "    p.id, " +
    "    p.title " +
    ") " +
    "from Post p " +
    "where p.createdOn > :fromTimestamp", PostDTO.class)
.setParameter(
    "fromTimestamp",
    Timestamp.from(
        LocalDate.of(2020, 1, 1)
            .atStartOfDay()
            .toInstant(ZoneOffset.UTC)
    )
)
.getResultList();

Basically, I want to be able to use the simple Java class name by default, instead of having to supply the fully-qualified class name for every JPA constructor expression.

Hibernate Integrator

In order to be able to use the simple Java class name, we need to supply a custom org.hibernate.integrator.spi.Integrator which provides us access to the bootstrapping Metadata.

public class ClassImportIntegrator implements Integrator {

    private final List<Class> classImportList;

    public ClassImportIntegrator(
            List<Class> classImportList) {
        this.classImportList = classImportList;
    }

    @Override
    public void integrate(
            Metadata metadata,
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {
        for(Class classImport : classImportList) {
            metadata.getImports().put(
                classImport.getSimpleName(),
                classImport.getName()
            );
        }
    }

    @Override
    public void disintegrate(
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {}
}

Now, we only have to provide the ClassImportIntegrator to Hibernate, and we will be able to run JPA projection queries that use the DTO simple class name.

Bootstrapping JPA programmatically with Hibernate

If we’re bootstrapping JPA programmatically using the Hibernate, we can pass the ClassImportIntegrator via thehibernate.integrator_provider` configuration property:

Properties properties = new Properties();
properties.put(
    "hibernate.integrator_provider", 
    (IntegratorProvider) () -> Collections.singletonList(
        new ClassImportIntegrator(Arrays.asList(PostDTO.class))
    )
);

EntityManagerFactory entityManagerFactory = new EntityManagerFactoryBuilderImpl(
    new PersistenceUnitInfoDescriptor(
        persistenceUnitInfo
    ), 
    properties
).build();

Now, it would be great if the hibernate.integrator_provider took the fully qualified name of the class implementing the IntegratorProvider interface.

For this, I created the HHH-13614 Jira issue, and in the good spirit of open-source software development, I submitted a Pull Request as well. Let’s hope the Hibernate development team integrate it sooner than later.

Spring framework

If you’re using Spring or Spring Boot, you can choose to use either JPA or the native Hibernate API for the data access layer.

Spring and JPA

To bootstrap JPA with Spring, you need to use the 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(
                Arrays.asList(
                    PostDTO.class
                )
            )
        )
    );
    
    emf.setJpaProperties(properties);

    return emf;
}

Notice how we passed the hibernate.integrator_provider configuration property to the LocalContainerEntityManagerFactoryBean via its setJpaProperties method.

Spring and Hibernate

To bootstrap the native Hibernate with Spring, you need to use the SessionactoryBean:

@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(
            Arrays.asList(
                PostDTO.class
            )
        )
    );
    
    return sf;
}

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Seize the deal! 50% discount. Seize the deal! 50% discount.

Conclusion

Omitting the DTO package name in a JPA query is definitely that type of enhancement most Java developers wanted to have for a long time, as demonstrated by the positive reactions I got on this tweet.

Enjoy running simpler DTO projection queries with JPA and Hibernate.

FREE EBOOK

4 Comments on “How to write a compact DTO projection query with JPA

  1. Dang, I was planning on using it on my new project 😀 Wasn’t it a much cleaner solution than this? Seems like a pain to manage… Can you include DTOs in xml (persistence.xml or something similar)? Sorry, I do not know a lot about JPA and what I was thaught was probably bad 😀 Your blog helped me a lot though 🙂

    • You can still use ResultTransformer as it’s much more powerful than JPA constructor expression. Just that you’ll have to do a refactoring the next time you migrate to Hibernate 6.

    • ResultTransformer is deprecated, and you’ll have to change your code when it will be removed in future versions of Hibernate.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.