How to write a compact DTO projection query with JPA
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.
So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!
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.
How to write a compact DTO projection query with JPA @vlad_mihalceahttps://t.co/EDgXuEbsFX pic.twitter.com/rAf1VZgtAA
— Java (@java) September 13, 2019
Domain Model
Let’s consider the following Post
entity and its associated PostDTO
Value Object.
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", LocalDate.of(2016, 1, 1).atStartOfDay() ) .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", LocalDate.of(2016, 1, 1).atStartOfDay() ) .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.
In order to be able to use the simple Java class name, we need to use the ClassImportIntegrator
utility provided by the Hypersistence Utils project:
Declarative configuration
If you are using a declarative configuration, then you first need to create a class implementing the Hibernate IntegratorProvider
, which returns the configured ClassImportIntegrator
instance:
public class ClassImportIntegratorIntegratorProvider implements IntegratorProvider { @Override public List<Integrator> getIntegrators() { return List.of( new ClassImportIntegrator( List.of( PostDTO.class ) ) ); } }
Afterward, you need to set the hibernate.integrator_provider
configuration property to the fully-qualified name of the ClassImportIntegratorIntegratorProvider
.
If you’re using Spring Boot, you can declare the hibernate.integrator_provider
property in the application.properties
configuration file like this:
spring.jpa.properties.hibernate.integrator_provider=your.awesome.app.ClassImportIntegratorIntegratorProvider
If you’re Java EE, you can set the hibernate.integrator_provider
property in the persistence.xml
JPA configuration file, like this:
<property name="hibernate.integrator_provider" value="your.awesome.app.ClassImportIntegratorIntegratorProvider"
That’s it!
YouTube Video
I also published a YouTube video about compact JPA projections, so if you enjoy the topic, you are going to love this video episode:
Programmatic configuration
You can also configure the hibernate.integrator_provider
property programmatically, using a Spring Java-based configuration, either via the JPA or the native Hibernate API bootstrapping strategies.
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( List.of( 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 LocalSessionFactoryBean
:
@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; }
Using relative package names
By default, the ClassImportIntegrator
will register the provided DTOs using their simple class name. However, if you have multiple DTOs with the same name located in different packages, you need to register the relative package name to differentiate between different DTO classes.
The fully-qualified name of the PostDTO
class is com.vladmihalcea.book.hpjp.hibernate.forum.dto.PostDTO
. Therefore, we can configure the ClassImportIntegrator
to exclude the com.vladmihalcea.book.hpjp.hibernate
path, so we can reference the PostDTO
using the remaining relative path, forum.dto.PostDTO
.
To exclude a package prefix, you need to call the excludePath
method, as follows:
List<PostDTO> 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();
Cool, right?
I'm running an online workshop on the 11th of October about High-Performance SQL.If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Omitting the DTO package name in a JPA query is definitely the type of enhancement most Java developers wanted to have for a long time, as demonstrated by the positive reactions I got on this tweet.
Next week, I'm going to show you how you can omit the package name when doing DTO projections with @Java Persistence JPQL.
— Vlad Mihalcea (@vlad_mihalcea) September 7, 2019
Who's interested in this awesome tip? pic.twitter.com/B249xV47nZ
Enjoy running simpler DTO projection queries with JPA and Hibernate.
