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 TupleTransformer
s or multiple ResultListTransformer
s 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: