Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll learn how to project entity properties using JPA and Hibernate.

2. The Entity

First, let’s look at the entity we will be using throughout this article:

@Entity
public class Product {
    @Id
    private long id;
    
    private String name;
    
    private String description;
    
    private String category;
    
    private BigDecimal unitPrice;

    // setters and getters
}

This is a simple entity class representing a product with various properties.

3. JPA Projections

Though the JPA spec doesn’t mention projections explicitly, there are many cases where we find them in concept.

Typically, a JPQL query has a candidate entity class. The query, on execution, creates objects of the candidate class — populating all their properties using the data retrieved.

But, it’s possible to retrieve a subset of the properties of the entity, or, that is, a projection of column data.

Apart from column data, we can also project the results of grouping functions.

3.1. Single-Column Projections

Let’s suppose we want to list the names of all products. In JPQL, we can do this by including only the name in the select clause:

Query query = entityManager.createQuery("select name from Product");
List<Object> resultList = query.getResultList();

Or, we can do the same with CriteriaBuilder:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<String> query = builder.createQuery(String.class);
Root<Product> product = query.from(Product.class);
query.select(product.get("name"));
List<String> resultList = entityManager.createQuery(query).getResultList();

Because we are projecting a single column that happens to be of type String, we expect to get a list of Strings in the result. Hence, we’ve specified the candidate class as String in the createQuery() method.

Since we want to project on a single property, we’ve used the Query.select() method. What goes here is which property we want, so in our case, we’d use the name property from our Product entity.

Now, let’s look at a sample output generated by the above two queries:

Product Name 1
Product Name 2
Product Name 3
Product Name 4

Note that if we’d used the id property in the projection instead of the name, the query would have returned a list of Long objects.

3.2. Multi-Column Projections

To project on multiple columns using JPQL, we only have to add all the required columns to the select clause:

Query query = session.createQuery("select id, name, unitPrice from Product");
List resultList = query.getResultList();

But, when using a CriteriaBuilder, we’ll have to do things a bit differently:

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<Product> product = query.from(Product.class);
query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice"));
List<Object[]> resultList = entityManager.createQuery(query).getResultList();

Here, we’ve used the method multiselect() instead of select(). Using this method, we can specify multiple items to be selected.

Another significant change is the use of Object[]. When we select multiple items, the query returns an object array with a value for each item projected. This is the case with JPQL as well.

Let’s see what the data looks like when we print it:

[1, Product Name 1, 1.40]
[2, Product Name 2, 4.30]
[3, Product Name 3, 14.00]
[4, Product Name 4, 3.90]

As we can see, the returned data is a bit cumbersome to process. But, fortunately, we can get JPA to populate this data into a custom class.

Also, we can use CriteriaBuilder.tuple() or CriteriaBuilder.construct() to get the results as a list of Tuple objects or objects of a custom class respectively.

3.3. Projecting Aggregate Functions

Apart from column data, we might sometimes want to group the data and use aggregate functions, like count and average.

Let’s say we want to find the number of products in each category. We can do this using the count() aggregate function in JPQL:

Query query = entityManager.createQuery("select p.category, count(p) from Product p group by p.category");

Or we can use CriteriaBuilder:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
Root<Product> product = query.from(Product.class);
query.multiselect(product.get("category"), builder.count(product));
query.groupBy(product.get("category"));

Here, we’ve used CriteriaBuilder‘s count() method.

Using either of the above will produce a list of object arrays:

[category1, 2]
[category2, 1]
[category3, 1]

Apart from count(), CriteriaBuilder provides various other aggregate functions:

  • avg – Calculates the average value for a column in a group
  • max – Calculates the maximum value for a column in a group
  • min – Calculates the minimum value for a column in a group
  • least – Finds the least of the column values (for example, alphabetically or by date)
  • sum – Calculates the sum of the column values in a group

4. Hibernate Projections

Hibernate provides projection with a Criteria query. We can also access the entity field reference using the SingularAttribute class.

4.1. Single-Column Projections

First, let’s see how we can project a single column. We’ll use the example we saw earlier:

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<String> criteriaQuery = criteria.createQuery(String.class);
final Root<Product> root = criteriaQuery.from(Product.class);
final SingularAttribute<Product, String> name = Product_.name;
final Path<String> nameProjection = root.get(name);
criteriaQuery.select(nameProjection);

We’ve used the SingularAttribute class to specify the property that we want in the query result. Root.get() indicate the column which we want to select.

4.2. Multi-Column Projections

To project multiple columns, we’ll have to first create references of the SingularAttribute to project the columns. From the criteria builder, we can add multiple fields to the project by instantiating a Roo.get() object with the field name given by the SingularAttribute.

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<Object[]> criteriaQuery = criteria.createQuery(Object[].class);
final Root<Product> root = criteriaQuery.from(Product.class);
final SingularAttribute<Product, String> name = Product_.name;
final SingularAttribute<Product, Long> id = Product_.id;
final Path<String> nameProjection = root.get(name);
final Path<Long> idProjection = root.get(id);
criteriaQuery.multiselect(idProjection, nameProjection);

4.3. Projecting Aggregate Functions

Let’s see how we can implement the count example:

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<Object[]> criteriaQuery = criteria.createQuery(Object[].class);
final Root<Product> root = criteriaQuery.from(Product.class);
criteriaQuery.groupBy(root.get("category"));
criteriaQuery.multiselect(root.get("category"), criteria.count(root));

It’s important to note that we specify the GROUP BY in the CriteriaQuery object.

4.4. Using an Alias for a Projection

An interesting feature of the Hibernate Criteria API is the use of an alias for a projection.

This is especially useful when using an aggregate function, as we can then refer to the alias in the Criterion and Order instances:

final CriteriaBuilder criteria = session.getCriteriaBuilder();
final CriteriaQuery<Object[]> criteriaQuery = criteria.createQuery(Object[].class);
final Root<Product> root = criteriaQuery.from(Product.class);
criteriaQuery.groupBy(root.get("category"));
criteriaQuery.multiselect(root.get("category"), criteria.count(root));
criteriaQuery.orderBy(criteria.asc(criteria.count(root)));

5. Conclusion

In this article, we saw how to project entity properties using JPA and Hibernate.

It is important to note that Hibernate has deprecated its Criteria API from version 5.2 onwards in favour of the JPA CriteriaQuery API. But, this is only because the Hibernate team doesn’t have the time to keep two different APIs, that pretty much do the same thing, in sync.

And of course, the code used in this article can be found over on GitHub.

Course – LSD (cat=Persistence)

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

>> CHECK OUT THE COURSE
Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – Persistence (eBook) (cat=Persistence)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.