5 new features in Hibernate 5 every developer should know

By Thorben Janssen

Mapping, Query

The Hibernate team published the first Hibernate 5 release quite a while ago, and since then they introduced a bunch of new features. I explained several of them here on the blog, and it’s about time to have a look at the most popular ones. Based on the search traffic I get from google and my personal experience, the Java 8 support is the most popular change. But there are also some other features which might seem small but make common development tasks a bit easier.

Support classes of the Date and Time API as BasicTypes

The new Date and Time API was one of the most anticipated changes in Java 8. The old java.util.Date has a lot of issues which got finally fixed.

Unfortunately, JPA 2.1 and Hibernate 4 don’t provide direct support for it. But that’s not a huge issue. It just takes a few lines of code to implement an AttributeConverter that maps a LocalDate.

But obviously, the explicit support as a BasicType is still a lot better. Hibernate implemented that in version 5.0. Since then you don’t need any additional annotations or converter to persist the classes of the Date and Time API. You can use them in the same way as any other supported attribute types.

When you persist this Book entity, Hibernate automatically maps the LocalDate to a JDBC DATE and persists it in the database. I get into more details about the mapping of the different Date and Time API classes in: How to persist LocalDateTime & Co with Hibernate 5

Get query results as a Stream

Introducing a new method to give you your query result as a Stream doesn’t sound like a big thing. But the stream method of Hibernate’s Query interface provides an additional benefit that makes it especially interesting for huge result sets. It fetches the result set in multiple batches and uses Hibernate’s ScrollableResults implementation to scroll through it. This approach is a great fit if you use a Stream to process the result set records one by one and helps you to implement your use case efficiently.

You can use the new method since Hibernate 5.2 to get your query results. The following code snippet shows a simple example that selects all Book entities from the database and processes them as a Stream.

You can read more about Hibernate’s stream method in How to get query results as a Stream with Hibernate 5.2.

Fetch multiple entities by their primary key

Fetching multiple entities by their ID is a very common use case. Most developers either implement it with a loop that calls the find method of the EntityManager for each primary key or with a JPQL query that checks all primary key values in an IN clause. The first option requires Hibernate to perform a database query for each primary key. That can create huge performance issues. The second one allows you to fetch all entities with one query and is obviously the better option.

Hibernate 5.1 introduced a third option that avoids the issues of the first and is easier to use than the second one. The new MultiIdentifierLoadAccess interface provides a comfortable option to load multiple entities with one query. You just need to call the byMultipleIds method on the Hibernate Session to get a MultiIdentifierLoadAccess interface and provide a list of primary key values to the multiLoad method.

As you can see in the log output, Hibernate creates only 1 query to select the 3 records from the database. That’s the same query as you would implement yourself but the API is a lot easier to use.

Hibernate’s implementation also provides an additional advantage: It splits huge lists of primary key values into multiple batches. This is sometimes required because some databases limit the number of elements in an IN clause.

I explained the new MultiIdentifierLoadAccess interface in more details in How to fetch multiple entities by id with Hibernate 5.

Join unassociated entities in a query

You can easily join mapped associations between entities in JPQL queries. The mapping already provides the required join conditions, and you don’t need to provide them in your query.

But what about entities that map associated database tables but have no mapped association?

And that’s not a rhetorical question.

Most entity models don’t map all the possible associations. They only map the ones that seem to provide value in the domain model and not the ones where 2 database tables (seemingly by accident) store the same foreign key. It also happens quite often that a to-many association with lots of records on the many side doesn’t get mapped with Hibernate. The risk that someone calls the getter of the association and fetches several hundred or thousand entities is just too high.

That’s totally fine as long as you just look at the domain model. You probably don’t need these associations in the model. But that doesn’t mean that you don’t need them in one of your JPQL queries.

If that’s the case, you have 3 options:

  1. Model the association between the entities or
  2. Use a cross join in your JPQL query which might create performance issues or
  3. Use Hibernate’s proprietary JOIN clause.

I prefer option 3. It’s the easiest and most natural one.

Since Hibernate 5.1, you can use an SQL-like syntax to join entities without a modeled association.

@Repeatable annotations

Repeatable annotations are one of the smaller changes in Java 8. It allows you to annotate a class, attribute or interface with the same annotation multiple times. A typical JPA example in which you want to do that is defining multiple named queries for an entity.

So far, you had to annotate your entity with a @NamedQueries annotation which contained an array of @NamedQuery annotations. The annoying thing about that is that the @NamedQueries annotation is just a container. It doesn’t provide any value on its own.

Since Hibernate 5.2, you don’t have to do that anymore. At least not as long as you use Hibernate’s version of the org.hibernate.annotations.NamedQuery annotation. @NamedQuery and a lot of other Hibernate annotations are now repeatable and can be assigned multiple times. As you can see in the following code snippet, that makes the code easier to read and is much more comfortable to use.

Summary

Hibernate 5 brought a lot of changes, but these 5 were the ones which I like the best and which I actively used the most, so far. Especially the support for the Date and Time API and the new MultiIdentifierLoadAccess interface have been helpful.

Hibernate obviously introduced more than just these 5 changes. There is a new bootstrapping API, for example, and the Session interface now extends JPA’s EntityManager. These changes are all nice and interesting, but they didn’t have an impact on my projects because I’m working in a Java EE environment.

That might be completely different for you. So, which are your favorite changes in Hibernate 5? Are there any features which you don’t want to miss in your daily work? Please tell me about them in the comments.


Tags

Mapping, Query


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.

  1. In the section “Get query results as a Stream” your example code contains:

    List books = session.createQuery(“SELECT b FROM Book b”, Book.class).getResultList();
    books.stream()…

    But this is not the way of using the new Hiberante functionality you’ve described. It should be:

    Stream books = session.createQuery(“SELECT b FROM Book b”, Book.class).stream();
    books.map(b -> b.getTitle() + ” was published on ” + b.getPublishingDate())
    .forEach(m -> log.info(m));

    (from you website //thorben-janssen.com/get-query-results-stream-hibernate-5/)

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