The best way to use a Hibernate ResultTransformer
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, I’m going to present the best way to use a Hibernate ResultTransformer so that you can customize the result set of a given JPA or Hibernate query.
As I already explained, the Hibernate ResultTransformer is a very powerful mechanism, allowing you to customize a JPQL, Criteria API, or native SQL query result set in any possible way.
Domain Model
Let’s assume we have the following Post
entity:
The Post
entity is mapped as follows:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id private Long id; private String title; @Column(name = "created_on") private LocalDate createdOn; public Long getId() { return id; } public Post setId(Long id) { this.id = id; return this; } public String getTitle() { return title; } public Post setTitle(String title) { this.title = title; return this; } public LocalDate getCreatedOn() { return createdOn; } public Post setCreatedOn(LocalDate createdOn) { this.createdOn = createdOn; return this; } }
Notice that the createdOn
attribute is of the LocalDate
type, which is supported by JPA 2.2 and Hibernate since version 5.
Since the Post
entity uses the Fluent-style API, it’s much easier to create a Post
entity and pass it directly to the persist
method, as illustrated by the following example:
entityManager.persist( new Post() .setId(1L) .setTitle( "High-Performance Java Persistence " + "eBook has been released!") .setCreatedOn(LocalDate.of(2016, 8, 30)) ); entityManager.persist( new Post() .setId(2L) .setTitle( "High-Performance Java Persistence " + "paperback has been released!") .setCreatedOn(LocalDate.of(2016, 10, 12)) ); entityManager.persist( new Post() .setId(3L) .setTitle( "High-Performance Java Persistence " + "Mach 1 video course has been released!") .setCreatedOn(LocalDate.of(2018, 1, 30)) ); entityManager.persist( new Post() .setId(4L) .setTitle( "High-Performance Java Persistence " + "Mach 2 video course has been released!") .setCreatedOn(LocalDate.of(2018, 5, 8)) ); entityManager.persist( new Post() .setId(5L) .setTitle( "Hypersistence Optimizer has been released!") .setCreatedOn(LocalDate.of(2019, 3, 19)) );
Counting posts by year
Now, we want to count the number of posts published every year, so we can use the following JPQL query:
select YEAR(p.createdOn) as year, count(p) as postCount from Post p group by YEAR(p.createdOn) order by YEAR(p.createdOn)"
However, since this query returns a projection, we want to encapsulate it in a DTO, like the following PostCountByYear
class:
public class PostCountByYear { private final int year; private final int postCount; public PostCountByYear( int year, int postCount) { this.year = year; this.postCount = postCount; } public int getYear() { return year; } public int getPostCount() { return postCount; } }
One option to populate the PostCountByYear
is via a JPA Constructor Result, as explained in this article.
However, the ResultTransformer
is even more flexible, as it allows us to aggregate data any way we want, and even choose the returning type.
Hibernate ResultTransformer
To use the ResultTransformer
, we need to unwrap the JPA Query
to the Hibernate org.hibernate.query.Query
, which gives us access to the setResultTransformer
method:
List<PostCountByYear> postCountByYearMap = (List<PostCountByYear>) entityManager.createQuery(""" select YEAR(p.createdOn) as year, count(p) as postCount from Post p group by YEAR(p.createdOn) order by YEAR(p.createdOn) """) .unwrap(org.hibernate.query.Query.class) .setResultTransformer( new ResultTransformer() { @Override public Object transformTuple( Object[] tuple, String[] aliases) { return new PostCountByYear( ((Number) tuple[0]).intValue(), ((Number) tuple[1]).intValue() ); } @Override public List transformList(List tuples) { return tuples; } } ) .getResultList();
Don’t get scared of the fact that the
setResultTransformer
is deprecated in Hibernate 5.2. Theoretically, it shouldn’t have been deprecated as there is no alternative to use instead.The reason it was deprecated is that, in Hibernate 6, a
@FunctionInterface
alternative will be provided, but the migration will probably be straightforward, so don’t discard theResultTransformer
just because it got deprecated a little too soon.
The problem with the default ResultTransformer
is that we cannot use a Java lambda to transform the Object[]
tuple representing a record in the JDBC ResltSet
.
ListResultTransformer, a much better Hibernate ResultTransformer
The ResultTransformer
interface should have defined the transformTuple
method only, from the very beginning. The transformList
was added just to accommodate the DistinctRootEntityResultTransformer
.
It would have been a much better design if, instead of adding the transformList
method into the ResultTransformer
interface and have most implementations, simply return the unmodified tuples list, a new ListResultTransformer
interface had bee added to extend the ResultTransformer
and define the transformList
method.
We can easily fix this problem by defining a ListResultTransformer
interface like this:
@FunctionalInterface public interface ListResultTransformer extends ResultTransformer { /** * Default implementation returning the tuples list as-is. * * @param tuples tuples list * @return tuples list */ @Override default List transformList(List tuples) { return tuples; } }
You don’t even need to define it. You can get it from the Hypersistence Utils project.
Just add the Hypersistence Utils dependency according to the Hibernate version you are using, and start using it:
<dependency> <groupId>io.hypersistence</groupId> <artifactId>hypersistence-utils-hibernate-60</artifactId> <version>${hypersistence-utils.version}</version> </dependency>
With the ListResultTransformer
, we can rewrite the previous ResultTransformer
example like this:
List<PostCountByYear> postCountByYearMap = (List<PostCountByYear>) entityManager.createQuery(""" select YEAR(p.createdOn) as year, count(p) as postCount from Post p group by YEAR(p.createdOn) order by YEAR(p.createdOn) """) .unwrap(org.hibernate.query.Query.class) .setResultTransformer( (ListResultTransformer) (tuple, aliases) -> new PostCountByYear( ((Number) tuple[0]).intValue(), ((Number) tuple[1]).intValue() ) ) .getResultList();
Much better, right?
Although the aforementioned example used a JPQL query, the ResultTransformer
can also be applied to Criteria API or native SQL queries, so it’s not limited to JPQL queries only.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
The ResultTransformer
is a very powerful mechanism, allowing you to customize a JPA or Hibernate query result set programmatically.
Even if the default ResultTransformer
has an initial design flaw that prevents it from being used as a FunctionalInterface
, we can overcome this limitation by using the ListResultTransformer
supplied by the Hypersistence Utils open-source project.
