Imagine having a tool that can automatically detect JPA and Hibernate performance issues.
Hypersistence Optimizer is that tool!
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;
}
}
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 the ResultTransformer 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;
}
}
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.
Online Workshops
If you enjoyed this article, I bet you are going to love my upcoming Online Workshops.
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 hibernate-types open-source project.
great big help! thank you!