The best way to use a Hibernate ResultTransformer

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, 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:

Post entity used for the Hibernate ResultTransformer example

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 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;
    }
}

You don’t even need to define it. You can get it from the hibernate-types open-source project.

Just add the hibernate-types dependency according to the Hibernate version you are using, and start using it:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.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 hibernate-types open-source project.

Transactions and Concurrency Control eBook

2 Comments on “The best way to use a Hibernate ResultTransformer

    • Yes, of course. The ResultTransformer works for any type of query, be it JPQL or native SQL.

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.