Why you should use the Hibernate ResultTransformer to customize result set mappings
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
Introduction
JPA queries allow you to fetch either entities or DTO projections. However, sometimes you want a combined result set as illustrated in this Hibernate forum question.
Domain Model
Assuming you have the following entities:
The relationship between the two entities is not materialized in a @ManyToOne association. However, both entities share a locale attribute which we can use to form a join between the two.
Returning an entity in a DTO projection
As I explained before, DTO projections are suitable for read-only transactions and fetching data that is not meant to be modified.
However, there might be use cases when you want to select an entity inside your DTO projection. Therefore, considering we have the following DTO projection:
public class PersonAndCountryDTO{
private final Person person;
private final String country;
public PersonAndCountryDTO(
Person person,
String country) {
this.person = person;
this.country = country;
}
public Person getPerson() {
return person;
}
public String getCountry() {
return country;
}
}
When you execute a JPQL query like this one:
List<PersonAndCountryDTO> personAndAddressDTOs = entityManager.createQuery(
"select new " +
" com.vladmihalcea.book.hpjp.hibernate.query.dto.PersonAndCountryDTO(" +
" p, " +
" c.name" +
" ) " +
"from Person p " +
"join Country c on p.locale = c.locale " +
"order by p.id", PersonAndCountryDTO.class)
.getResultList();
Hibernate generates the following SQL queries:
SELECT p.id AS col_0_0_,
c.name AS col_1_0_
FROM Person p
INNER JOIN
Country c
ON
( p.locale = c.locale )
ORDER BY
p.id
SELECT p.id AS id1_1_0_,
p.locale AS locale2_1_0_,
p.name AS name3_1_0_
FROM Person p
WHERE p.id = 3
SELECT p.id AS id1_1_0_,
p.locale AS locale2_1_0_,
p.name AS name3_1_0_
FROM Person p
WHERE p.id = 4
The Hibernate 5.2 implementation of the DTO projection cannot materialize the DTO projection from the ResultSet without executing a secondary query. However, this is very bad to performance since it can lead to N+1 query issues.
This HQL limitation has been discussed, and Hibernate 6.0 new SQM parser might address this issue, so stay tuned!
ResultTransformer
However, you are not limited to using JPA alone. Hibernate offers many enhancements that have no direct equivalent in the standard. One of these enhancements is the ResultTransformer mechanism which allows you to customize the ResultSet any way you like.
List<PersonAndCountryDTO> personAndAddressDTOs = entityManager
.createQuery(
"select p, c.name " +
"from Person p " +
"join Country c on p.locale = c.locale " +
"order by p.id")
.unwrap( org.hibernate.query.Query.class )
.setResultTransformer(
new ResultTransformer() {
@Override
public Object transformTuple(
Object[] tuple,
String[] aliases) {
return new PersonAndCountryDTO(
(Person) tuple[0],
(String) tuple[1]
);
}
@Override
public List transformList(List collection) {
return collection;
}
}
)
.getResultList();
There are two things to consider for this query:
- The
unwrapmethod is used to cast the JPAjavax.persistence.Queryto the Hibernate-specificorg.hibernate.query.Queryso that we gain access to thesetResultTransformermethod. - The
ResultTransformercomes with a legacy definition which is not following the Functional Interface syntax. Hence, we cannot use a lambda in this example. Hibernate 6.0 aims to overcome this issue, so that’s why the Hibernate ORM 5.2ResultTransformeris deprecated. Nevertheless, an alternative will be provided, so the concept we are discussing in this article is going to stand still even in Hibernate 6.
When running the aforementioned Hibernate ResultTransformer query, Hibernate generates the following output:
SELECT p.id AS col_0_0_,
c.name AS col_1_0_,
p.id AS id1_1_,
p.locale AS locale2_1_,
p.name AS name3_1_
FROM Person p
INNER JOIN
Country c
ON
( p.locale = c.locale )
ORDER BY
p.id
Much better!
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
While the JPA NEW select clause is fine for trivial DTO projections, the ResultTransformer it allows you to customize the result set any way you like. In the particular use case we tested in this article, the ResultTransformer is also much more efficient as well, generating a single SQL query instead of N+1 ones.







