Hibernate - Using TupleTransformer & ResultListTransformer

This tutorial shows you how to use Hibernate's TupleTransformer and ResultListTransformer.

Hibernate allows you to define queries in various ways. Sometimes you may want to modify the query results. For example, formatting the values of some fields or removing rows that doesn't meet a criteria. If you use Hibernate, it's possible to add transformers to be applied to the query results. By adding transformers to process the result, your code becomes cleaner since the results returned by the query are already in the format that you want.

The traditional way to define a transformer in hibernate is using a ResultTransformer. However, it has been deprecated since version 5.2. As the replacements, Hibernate 6 introduced two new types of transformers, TupleTransformer and ResultListTransformer. Below I'm going to show you how to use them.

Using TupleTransformer

For example, we have the following entity.

  @Entity
  @Table(name = "users")
  public class User {
  
    @Id
    @GeneratedValue
    @GenericGenerator(name = "UUID", type = UuidGenerator.class)
    private UUID id;
  
    private String name;
  
    private Boolean isActive;
  
    private String address;
  
    private int postalCode;
  
    private String city;
  
    private String country;
  
    // Constructors, getters, setters, and builder are not included
  }

When fetching the rows from the database, there is a requirement to return the results in another format where the address-related fields are combined into an object, as shown in the class below.

  public class UserDto {
  
    private UUID id;
  
    private String name;
  
    private Boolean isActive;
  
    private AddressDto address;
  
    // Constructors, getters, setters, and builder are not included
  
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Getter
    @Setter
    public static class AddressDto {
  
      private String address;
  
      private int postalCode;
  
      private String city;
  
      private String country;
  
      // Constructors, getters, setters, and builder are not included
    }
  }

In Hibernate, you can create a TupleTransformer, which is used to define a transformation to be applied to each query result before the result is combined into a List. Each result is received as an Object[] and you can map it to any type.

To create and apply a TupleTransformer, first create a Hibernate Query object. The class is the one in the org.hibernate.query package. It can be done by creating a JPA Query instance using EntityManager.createQuery and converting the object to a Hibernate Query. Then, you can pass a function as the argument of setTupleTransformer. Below is the interface that defines the signature of the function.

  public interface TupleTransformer<T> {
    T transformTuple(Object[] tuple, String[] aliases);
  }

The passed function is required to have two parameters. The type of the first one is Object[] whose elements are the values of a row. The other parameter is aliases whose elements are the aliases of the corresponding element in the Object[].

Below is an example of a transformer that converts a User object to a UserDto object.

  @SuppressWarnings("unchecked")
  public List<UserDto> findAllByCountryCustom(String country) {
    String sql = "SELECT u FROM User u "
        + " WHERE u.country = :country";

    Query query = this.entityManager.createQuery(sql)
        .unwrap(org.hibernate.query.Query.class)
        .setTupleTransformer(((tuple, aliases) -> {
          User user = (User) tuple[0];

          return UserDto.builder()
              .id(user.getId())
              .name(user.getName())
              .isActive(user.getIsActive())
              .address(UserDto.AddressDto.builder()
                  .address(user.getAddress())
                  .postalCode(user.getPostalCode())
                  .city(user.getCity())
                  .country(user.getCountry().toUpperCase())
                  .build()
              )
              .build();
        }))
        .setParameter("country", country);

    return (List<UserDto>) query.getResultList();
  }

Using ResultListTransformer

For example, there is another requirement to only include active users in the result. Let's assume it can't be done using the WHERE clause. As a result, we have to filter the list of rows returned from the database. If you want a transformer that's applied on the combined List result, you can define a ResultListTransformer instead.

  public interface ResultListTransformer<T> {
    List<T> transformList(List<T> resultList);
  }

You also have to create a Hibernate Query object. Then, use the setResultListTransformer method to pass the transformer function. The function has a parameter whose type is List<T> with the same return type.

  @SuppressWarnings("unchecked")
  public List<User> findAllByCountryCustom(String country) {
    String sql = "SELECT u FROM User u "
        + " WHERE u.country = :country";

    Query query = this.entityManager.createQuery(sql)
        .unwrap(org.hibernate.query.Query.class)
        .setResultListTransformer(list -> ((List<User>) list).stream()
            .filter(User::getIsActive)
            .collect(Collectors.toList())
        )
        .setParameter("country", country);

    return (List<User>) query.getResultList();
  }

Using Both TupleTransformer and ResultListTransformer

It's also possible to use both of them. The TupleTransformer will be applied first in that case. However, you cannot have multiple TupleTransformers or multiple ResultListTransformers since the last one will replace the previous ones.

  @SuppressWarnings("unchecked")
  public List<UserDto> findAllByCountryCustom(String country) {
    String sql = "SELECT u FROM User u "
        + " WHERE u.country = :country";

    Query query = this.entityManager.createQuery(sql)
        .unwrap(org.hibernate.query.Query.class)
        .setTupleTransformer(((tuple, aliases) -> {
          User user = (User) tuple[0];

          return UserDto.builder()
              .id(user.getId())
              .name(user.getName())
              .isActive(user.getIsActive())
              .address(UserDto.AddressDto.builder()
                  .address(user.getAddress())
                  .postalCode(user.getPostalCode())
                  .city(user.getCity())
                  .country(user.getCountry().toUpperCase())
                  .build()
              )
              .build();
        }))
        .setResultListTransformer(list -> ((List<UserDto>) list).stream()
            .filter(UserDto::getIsActive)
            .collect(Collectors.toList())
        )
        .setParameter("country", country);

    return (List<UserDto>) query.getResultList();
  }

Summary

Starting from Hibernate 6, you can define a TupleTransformer or a ResultListTransformer for processing the query result. The TupleTransformer is applied on each result before combined into a List. Meanwhile, ResultListTransformer can be used to process the result that's already combined into a List. You can use both of them if necessary.

You can also read about: