Featured Image with Sidebar

Hibernate’s Query APIs

By Thorben Janssen


When working with Hibernate, you can choose between multiple APIs to query data from your database. Some of them are very flexible and provide almost unlimited options to filter and transform your query results. Others are specifically designed to implement specific tasks as easily and quickly as possible.

In this article, I will show you 6 different query mechanisms supported by Hibernate. 3 of them are flexible, and 3 are specialized for a specific use case. If you’re using a different JPA implementation, you should at least read about the 3 flexible and the 1st specialized options. These are defined by the JPA specification and supported by every compliant implementation.

Flexible Query Mechanisms

For most uses cases, you will use one of the following flexible query mechanisms. They enable you to define a query using the projection and filter criteria that suite your use case the best.

JPQL Query

JPQL is JPA’s query language. It is very similar to SQL. Its main difference is that you specify your query based on your domain model. The following code snippet shows a simple example that selects all Book entities with the provided phrase in their title.

TypedQuery<Book> q = em.createQuery("SELECT b FROM Book b WHERE b.title like :title", Book.class);
q.setParameter("title", "%Hibernate%");
List<Book> books = q.getResultList();

When you execute this query, your persistence provider, e.g., Hibernate, generates an SQL query, sets the bind parameter values, and executes it.

While generating the SQL statement, Hibernate adjusts it to the dialect supported by your database. That makes your JPQL query database-independent, but it also limits you to the feature set supported by your persistence provider.

12:32:24,711 DEBUG [org.hibernate.SQL] - 
    select
        book0_.id as id1_1_,
        book0_.price as price2_1_,
        book0_.publisherid as publishe6_1_,
        book0_.publishingDate as publishi3_1_,
        book0_.title as title4_1_,
        book0_.version as version5_1_ 
    from
        Book book0_ 
    where
        book0_.title like ? 
12:32:24,714 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [1] as [VARCHAR] - [%Hibernate%]

You can do a lot more with JPQL. E.g., you can choose different projections in the SELECT clause, create JOIN clauses over multiple tables, order and group the query result, and call database functions. I explain all of that in great detail in my Guide to JPQL.

Criteria API

The Criteria API is a set of interfaces and classes that you can use to specify a query. It supports the same feature set as JPQL. The following code snippet defines the same query as the example in the JPQL section.

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> root = cq.from(Book.class);

ParameterExpression<String> title = cb.parameter(String.class);
cq.where(cb.like(root.get(Book_.title), title));

TypedQuery<Book> query = em.createQuery(cq);
query.setParameter(title, "%Hibernate%");
List<Book> books = query.getResultList();

As you can see, using the Criteria API requires more code than a JPQL query. But it is much easier to adjust based on user input and easier to refactor.

When you execute this query, Hibernate generates the same query as for the previously shown JPQL query.

12:55:38,455 DEBUG [org.hibernate.SQL] - 
    select
        book0_.id as id1_1_,
        book0_.price as price2_1_,
        book0_.publisherid as publishe6_1_,
        book0_.publishingDate as publishi3_1_,
        book0_.title as title4_1_,
        book0_.version as version5_1_ 
    from
        Book book0_ 
    where
        book0_.title like ? 
12:55:38,459 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [1] as [VARCHAR] - [%Hibernate%]

You now might ask yourself why or when you should use the Criteria API if you could do the same with a JPQL query. The Criteria API provides 2 main benefits:

  1. You can define your query dynamically at runtime.
  2. If you combine it with JPA’s metamodel, your query definition gets typesafe and easy to refactor.

If none of these theoretical benefits provide real value for your specific use case, you should use a JPQL query. Otherwise, I recommend my Advanced Hibernate Online Training in which we discuss the Criteria API in great detail.

Native SQL Query

Hibernate executes native queries directly. It doesn’t need to generate the SQL statement. Due to this, you can use all query features supported by your database, and you are not limited by Hibernate. That makes native queries more powerful than JPQL queries and the Criteria API.

But that also requires you to create a native query statement that matches your database’s SQL dialect. If you deploy your application using different RDBMS, this might require different versions of the same query.

The following code snippet defines a native SQL query that selects all columns of all records in the book table in which the title field contains the word ‘Hibernate’. By providing the Book.class reference as the 2nd parameter to the query, I tell Hibernate to cast each record in the result set to a Book entity object.

Query q = em.createNativeQuery("SELECT * FROM book b WHERE title = :title", Book.class);
q.setParameter("title", "%Hibernate%");
List<Book> books = (List<Book>) q.getResultList();

If you want to learn more about this flexible and powerful approach to define your queries, you should take a closer look at my article Native Queries – How to call native SQL queries with JPA & Hibernate.

You can also provide the name of a @SqlResultSetMapping as the 2nd parameter. It tells Hibernate to map the query result to scalar values, entity object, DTOs object, and any combination of these 3. You can learn more about it here:

Special Purpose APIs

JPA and Hibernate also define a few APIs for special purposes. These are not as flexible as the ones we discussed in the previous section, but they make the implementation of some common use cases incredibly simple.

Query by ID

The find method on JPA’s EntityManager interface is a typical example of an API specifically designed for one use case. The only thing you can do with it is to load one entity object by its primary key value. This is a common task in every persistence layer, and the find method makes that as easy as possible.

Book b = em.find(Book.class, 1L);

As you’ve seen in the previous code snippet, you only need to call the find method with references to the entity’s class and the primary key value. Hibernate then generates the required SQL statement to select all columns mapped by the entity class of the record with the provided primary key.

17:29:29,675 DEBUG [org.hibernate.SQL] - 
    select
        book0_.id as id1_0_0_,
        book0_.publishingDate as publishi2_0_0_,
        book0_.title as title3_0_0_,
        book0_.version as version4_0_0_ 
    from
        Book book0_ 
    where
        book0_.id=?

This approach also works in the same way if your primary key consists of multiple attributes. This is called a composite primary key, and I explained the required mapping in a previous article. And Hibernate, of course, also handles inheritance mappings that map an entity class to multiple database tables.

Query by multiple IDs

In addition to the find method defined by the EntityManager, Hibernate’s Session interface also provides a byMultipleIds method. As you might have guessed from its name, this method enables you to fetch multiple records by their primary key value.

MultiIdentifierLoadAccess<Book> multi = session.byMultipleIds(Book.class);
List<Book> books = multi.multiLoad(1L, 2L, 3L);

When you call the multiLoad method, Hibernate generates an SQL statement with an IN clause to load all objects with one query.

17:38:46,979 DEBUG [org.hibernate.SQL] - 
    select
        book0_.id as id1_0_0_,
        book0_.publishingDate as publishi2_0_0_,
        book0_.title as title3_0_0_,
        book0_.version as version4_0_0_ 
    from
        Book book0_ 
    where
        book0_.id in (
            ?,?,?
        )

This might only look like a small usability feature. But as I have shown in a previous article, Hibernate automatically splits this operation into multiple batches, if you load more records than your DBMS supports parameters in an IN clause. You can also decide if Hibernate shall exclude primary key references to entity objects that are already part of the 1st level cache.

Query by natural ID

Another common use case is to load an entity by its natural ID. Using plain JPA, you need to write a query for it. Hibernate provides an annotation to declare the natural ID attributes and an API to fetch an entity by its natural ID.

Let’s use that in an example. You first need to annotate one or more entity attributes with @NaturalId.

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Long id;

	@NaturalId
	private String isbn;
	
	...
}

After you’ve done that, you can use the byNaturalId method defined by Hibernate’s Session interface to get an instance of the NaturalIdLoadAccess interface. On that object, you can then call the using method for each part of the natural id. After you’ve provided all id components, you can execute the query by calling the load method.

Book b = session.byNaturalId(Book.class)
				.using(Book_.isbn.getName(), "123-4567890123")
				.load();

Hibernate then generates a query to get the primary key value for the provided natural id. In the next step, it fetches the entity by the retrieved primary key.

17:51:05,486 DEBUG [org.hibernate.SQL] - 
    select
        book_.id as id1_0_ 
    from
        Book book_ 
    where
        book_.isbn=?
17:51:05,499 DEBUG [org.hibernate.SQL] - 
    select
        book0_.id as id1_0_0_,
        book0_.isbn as isbn2_0_0_,
        book0_.publishingDate as publishi3_0_0_,
        book0_.title as title4_0_0_,
        book0_.version as version5_0_0_ 
    from
        Book book0_ 
    where
        book0_.id=?

To improve this approach’s performance, Hibernate caches the mapping between the natural and the primary key and tries to resolve the entity reference against all activated caches.

You can learn more about these optimizations, the handling of composite natural IDs, and the caching of mutable natural IDs in my Guide to Hibernate’s NaturalID support.

Conclusion

As you’ve seen in this article, Hibernate supports the 3 different options defined by the JPA specification to define a query. All 3 of them are very flexible and enable you to filter the retrieved data by complex predicates, select information from multiple database tables, and transform it into the form that best suits your use case.

In addition to these 3 options, Hibernate also provides 3 APIs that were designed to fetch 1 entity by its primary key, multiple entities by their primary keys, and 1 entity by its natural ID. These APIs are not as flexible as the queries, but they make their supported operations as easy as possible.

About the author

Thorben is an independent consultant, international speaker, and trainer specialized in solving Java persistence problems with JPA and Hibernate.
He is also the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems.

Books and Courses

Coaching and Consulting

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.

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}