Most developers know that the JPA specification defines the string-based JPQL query language and that Hibernate extends it to support things like database-specific functions, window functions, and set-based operations. But most developers don’t know that since version 6, Hibernate has done the same for JPA’s Criteria API.
Extending an API is, of course, a little more complex than doing the same for a string-based query language. To extend the features of JPQL, the Hibernate team only needs to add more features to the parser of the query string and doesn’t need to change any of the official APIs. The extension of the Criteria API requires additional interfaces and new methods that return those interfaces.
Hibernate 6 handles this by providing the HibernateCriteriaBuilder interface, which extends JPA’s CriteriaBuilder interface, and by adding a method to its proprietary Session interface to get a HibernateCriteriaBuilder instance.
Before we talk about the HibernateCriteriaBuilder interface, we need to take one step back and take a look at the creation of a standard CriteriaQuery. And after that, I’ll show you how to get a HibernateCriteriaBuilder and the features it adds to JPA’s standard Criteria API.
The first step to using the Criteria API is always a call of the getCriteriaBuilder method on the EntityManager interface. That method returns an instance of JPA’s CriteriaBuilder, which you can use to create different parts of your query. In the following code snippet, I use it to create a very basic query that returns all ChessGame entities that a player played with the white pieces whose name ends on “anssen”.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
CriteriaBuilder cBuilder = em.getCriteriaBuilder();
CriteriaQuery<ChessGame> q = cBuilder.createQuery(ChessGame.class);
Root<ChessGame> game = q.from(ChessGame.class);
q.select(game);
q.where(cBuilder.like(game.get("playerWhite"), "%anssen"));
em.getTransaction().commit();
em.close();
As you can see, I use JPA’s CriteriaBuilder in 2 places:
JPA’s CriteriaBuilder interface provides lots of other methods that you can use to instantiate different kinds of CriteriaQuery objects, build more complex WHERE clauses, and call database functions. I explain all of that in more detail in the Advanced Hibernate course included in the Persistence Hub, and you can find a full list of all methods in the official Javadoc.
Hibernate’s HibernateCriteriaBuilder interface extends JPA’s CriteriaBuilder interface. Due to that, an implementation of the HibernateCriteriaBuilder supports the same methods, and you can use it in the same way I showed you in the previous section. In addition to that, the interface defines a few proprietary methods to support things like set operations and additional database functions.
The main difference you will recognize in your code is how you instantiate a HibernateCriteriaBuilder. The best way to instantiate it is by calling the getCriteriaBuilder method on Hibernate’s Session interface.
HibernateCriteriaBuilder cBuilder = em.unwrap(Session.class).getCriteriaBuilder();
CriteriaQuery<ChessGame> q = cBuilder.createQuery(ChessGame.class);
Root<ChessGame> game = q.from(ChessGame.class);
q.select(game);
q.where(cBuilder.like(game.get("playerWhite"), "%anssen"));
You can also cast a CriteriaBuilder interface to HibernateCriteriaBuilder. The cast is, of course, not type-safe, and it relies on the implementation detail that Hibernate uses the same class to implement both interfaces. I, therefore, recommend you get a Session and call the getCriteriaBuilder method.
HibernateCriteriaBuilder cBuilder = (HibernateCriteriaBuilder) em.getCriteriaBuilder();
CriteriaQuery<ChessGame> q = cBuilder.createQuery(ChessGame.class);
Root<ChessGame> game = q.from(ChessGame.class);
q.select(game);
q.where(cBuilder.like(game.get("playerWhite"), "%anssen"));
As you can see in the official Javadoc of the HibernateCriteriaBuilder interface, the interface defines many methods to build different parts of your query. Some of them are defined by JPA’s CriteriaBuilder; others are Hibernate-specific features. Here are some of the most interesting additions defined by the HibernateCriteriaBuilder interface.
INSERT INTO SELECT statements are a well-known SQL feature that enables you to insert data selected by a query as new records into a database table. Since version 6, Hibernate supports this for HQL statements, and Hibernate 6.1 will provide this feature as an extension to the Criteria API.
The HibernateCriteriaBuilder defines several methods to create Expressions that you can use to perform calculations, transform or extract information and get the current date or time. Here are a few examples:
Similar to the methods defined by JPA’s CriteriaBuilder interface that define expressions, you can use those methods to define your query’s projection or WHERE clause.
HibernateCriteriaBuilder cBuilder = em.unwrap(Session.class).getCriteriaBuilder();
CriteriaQuery<ChessGame> q = cBuilder.createQuery(ChessGame.class);
Root<ChessGame> game = q.from(ChessGame.class);
q.select(game);
q.where(cBuilder.equal(game.get("playedOn"), cBuilder.localDate()));
List<ChessGame> games = em.createQuery(q).getResultList();
Hibernate then includes these expressions in the generated SQL statement. Your database processes them and returns the result. This is important if you’re processing the returned values and rely on timezones or other localizations. In those situations, you need to ensure that your Java application and database use the same settings.
11:58:59,183 DEBUG [org.hibernate.SQL] - select c1_0.id,c1_0.playedOn,c1_0.playerBlack_id,c1_0.playerWhite_id,c1_0.version from ChessGame c1_0 where c1_0.playedOn=current_date
Hibernate also provides a few additional predicates, which you can use to define your WHERE clause. The most interesting ones are the different versions of ilike and notilike methods, which provide an easy way to define a case-insensitive LIKE or NOT LIKE expression.
HibernateCriteriaBuilder cBuilder = em.unwrap(Session.class).getCriteriaBuilder();
CriteriaQuery<ChessPlayer> q = cBuilder.createQuery(ChessPlayer.class);
Root<ChessPlayer> player = q.from(ChessPlayer.class);
q.select(player);
q.where(cBuilder.ilike(player.get("firstName"), "%ikar%"));
List<ChessPlayer> games = em.createQuery(q).getResultList();
games.forEach(p -> log.info(p));
16:32:13,147 DEBUG [org.hibernate.SQL] - select c1_0.id,c1_0.firstName,c1_0.lastName from ChessPlayer c1_0 where c1_0.firstName ilike ? escape ''
16:32:13,148 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [VARCHAR] - [%ikar%]
16:32:13,168 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=4, firstName=Hikaru, lastName=Nakamura]
And if you modeled an association as a java.util.Map, you can use the methods isMapEmpty, isMapNotEmpty, and mapSize to check if or how many elements that Map contains.
JPA’s CriteriaBuilder enables you to fetch the result set in the ascending or descending order of one or more entity attributes. In addition, the HibernateCriteriaBuilder also enables you to define the handling of null values and order by the result of an Expression, e.g., the result of a database function.
In addition to the asc and desc methods defined by JPA’s CriteriaBuilder, the HibernateCriteriaBuilder defines a 2nd version of each method that accepts a boolean as the 2nd method parameter. This boolean defines if null values shall be returned first.
HibernateCriteriaBuilder cBuilder = em.unwrap(Session.class).getCriteriaBuilder();
CriteriaQuery<ChessPlayer> q = cBuilder.createQuery(ChessPlayer.class);
Root<ChessPlayer> player = q.from(ChessPlayer.class);
q.select(player);
q.orderBy(cBuilder.asc(player.get("firstName"), true));
List<ChessPlayer> games = em.createQuery(q).getResultList();
games.forEach(p -> log.info(p));
17:24:56,003 DEBUG [org.hibernate.SQL] - select c1_0.id,c1_0.firstName,c1_0.lastName from ChessPlayer c1_0 order by c1_0.firstName asc nulls first
17:24:56,017 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=2, firstName=Fabiano, lastName=Caruana]
17:24:56,017 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=4, firstName=Hikaru, lastName=Nakamura]
17:24:56,017 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=1, firstName=Magnus, lastName=Carlsen]
17:24:56,017 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=3, firstName=Richard, lastName=Rapport]
If you want to define a more complex ORDER BY clause based on an Expression, you need to call one of the sort methods. They enable you to provide the Expression by which you want to sort the result, if you want to get the result in ascending or descending order and how you want to handle null values.
I use that in the following code snippet to get the query result in the ascending order of the length of the player’s first names. In this example, it doesn’t make any sense to define the handling of null values. But if you’re ordering your query result by a different Expression, you could provide a 3rd method parameter to define the handling of null values.
HibernateCriteriaBuilder cBuilder = em.unwrap(Session.class).getCriteriaBuilder();
CriteriaQuery<ChessPlayer> q = cBuilder.createQuery(ChessPlayer.class);
Root<ChessPlayer> player = q.from(ChessPlayer.class);
q.select(player);
q.orderBy(cBuilder.sort(cBuilder.length(player.get("firstName")), SortOrder.ASCENDING));
List<ChessPlayer> games = em.createQuery(q).getResultList();
games.forEach(p -> log.info(p));
08:15:10,477 DEBUG [org.hibernate.SQL] - select c1_0.id,c1_0.firstName,c1_0.lastName from ChessPlayer c1_0 order by character_length(c1_0.firstName) asc
08:15:10,493 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=1, firstName=Magnus, lastName=Carlsen]
08:15:10,493 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=4, firstName=Hikaru, lastName=Nakamura]
08:15:10,493 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=2, firstName=Fabiano, lastName=Caruana]
08:15:10,493 INFO [com.thorben.janssen.sample.TestSample] - ChessPlayer [id=3, firstName=Richard, lastName=Rapport]
Hibernate 6 also introduced support for set operations for HQL and Criteria queries. Using the HibernateCriteriaBuilder, you can now combine the result sets of 2 query statements using the methods union, unionAll, intersect, intersectAll, except, and exceptAll.
Here you can see an example that selects the firstName and lastName of all ChessPlayer in the 1st and the firstName and lastName of all ChessStreamer in the 2nd query and creates a union of both result sets. When using set operations, please keep in mind that all result sets need to follow the same structure.
HibernateCriteriaBuilder cBuilder = em.unwrap(Session.class).getCriteriaBuilder();
CriteriaQuery<Tuple> qPlayer = cBuilder.createTupleQuery();
Root<ChessPlayer> player = qPlayer.from(ChessPlayer.class);
qPlayer.multiselect(player.get("firstName").alias("firstName"), player.get("lastName").alias("lastName"));
CriteriaQuery<Tuple> qStreamer = cBuilder.createTupleQuery();
Root<ChessStreamer> streamer = qStreamer.from(ChessStreamer.class);
qStreamer.multiselect(streamer.get("firstName").alias("firstName"), streamer.get("lastName").alias("lastName"));
CriteriaQuery<Tuple> qPlayerAndStreamer = cBuilder.union(qPlayer, qStreamer);
List<Tuple> persons = em.createQuery(qPlayerAndStreamer).getResultList();
persons.forEach(t -> log.info(t.get("firstName") + ", " + t.get("lastName")));
As you can see in the log output, Hibernate generated an SQL statement that tells the database to apply the set operation union on the 2 result sets that contain the first and last names of all ChessPlayer and ChessStreamer.
17:43:05,857 DEBUG [org.hibernate.SQL] - select c1_0.firstName,c1_0.lastName from ChessPlayer c1_0 union select c2_0.firstName,c2_0.lastName from ChessStreamer c2_0
17:43:05,865 INFO [com.thorben.janssen.sample.TestSample] - Hikaru, Nakamura
17:43:05,865 INFO [com.thorben.janssen.sample.TestSample] - Fabiano, Caruana
17:43:05,865 INFO [com.thorben.janssen.sample.TestSample] - Magnus, Carlsen
17:43:05,865 INFO [com.thorben.janssen.sample.TestSample] - Richard, Rapport
17:43:05,865 INFO [com.thorben.janssen.sample.TestSample] - Levy, Rozman
17:43:05,865 INFO [com.thorben.janssen.sample.TestSample] - Ben, Finegold
As you saw in this article, Hibernate’s HibernateCriteriaBuilder interface extends JPA’s CriteriaBuilder interface and adds methods for Hibernate’s proprietary query features. These are:
By adding all these proprietary features, the HibernateCriteriaBuilder interface provides you with the same query features as Hibernate’s HQL query language, which extends JPA’s JPQL language. That enables you to easily switch between the 2 approaches and use the query definition you feel most comfortable with.