Hi,
I’m Thorben Janssen from thoughts-on-java.org. It’s quite a while since Hibernate 5.0 came
out and brought a bunch of changes. And since then the development team implemented even
more features. I explained several of them here on the channel and even more on my blog.
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 by far the most popular
change. But there are also some other features which might seem small but make common development tasks easier. Let’s begin with the support for the Date
and Time API. It 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. I explained that feature in great detail in
a previous video. So, let’s take a look at the most important parts and you can watch
that video afterwards, if you want to dive deeper. Here you can see a simple entity with attributes of type LocalDate, LocalDateTime, and Duration.
All 3 of them were introduced with the new Date and Time API in Java 8. As you can see,
I didn’t apply any additional annotations to define the mapping to the database columns.
Hibernate already has all the information it needs.
As you can see in this test case, you can use these attributes in the same way as any
other attributes. I instantiate a new entity, call the setter methods to initialize the
attributes and persist the entity. And down here, I use the LocalDate attribute with a
bind parameter in the WHERE clause of a JPQL query. Let’s run this test and check how Hibernate handles the attributes of the Date and Time
API. The test case was successful. Hibernate mapped
the Java types to the correct JDBC types without any additional mapping annotations. It mapped
the LocalDate to a JDBC DATE, the LocalDateTime to a TIMESTAMP and the DURATION to a BIGINT.
And when I executed the query with the bind parameter of type LocalDate, Hibernate mapped
the bind parameter value in the same way. OK, let’s take a look at another addition
in Hibernate 5. You can now get query results as a Stream. And I know, introducing a new
method to give you your query result as a Stream doesn’t sound like a big thing. You could call the
stream() method of the Collection interface to get a Stream with your query results. But
the new 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 cases. Let’s jump into the IDE and try it out. The new method is part of Hibernate’s Query interface. That means you can use it with
all kinds of projections in JPQL, Criteria and native SQL queries.
Here you can see a simple example that uses a JPQL query to retrieve all Book entities
from the database. I would expect that this query returns a lot of entities, if you run
it on a production database. So, it’s a good idea to not fetch all of them at once
to avoid performance issues. But before we use the new stream() method,
let’s get the result set as a List, transform it into a Stream and write all entities to
the log file. That was the typical approach before Hibernate 5.2 extended the API. As you can see here, Hibernate gets all records
of the result set from the database and maps them to entities before it starts processing
the Stream. Let’s see how that changes, when I use the
stream method of the Query interface instead of the getResultList method. Here you can see that Hibernate’s log messages
are mixed with our own messages. Hibernate doesn’t fetch and map all records upfront.
It does that only, when the Stream processes the next record. That saves a lot of resources
for huge result sets. Let’s get to the next feature. This one
is not related to Java 8 but it makes it a lot easier to fetch multiple entities by their
primary key. Most developers either implement that 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 that 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. Here you can see the same code as I showed
you on the slide. It loads the Book entities with ids 1, 2 and 3. And as you can see in the log output, Hibernate
generated only 1 query to get the entities from the database. That’s a lot better than
performing a query for each entity and it was much easier than writing the query yourself. This is something I missed for years. Hibernate 5 finally introduced join statements for unassociated entities. In JPA and older Hibernate versions, 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 you can’t do that for unassociated entities.
And 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. So, it’s happens quite often, that you could
join two tables in a query but you can’t do it with JPQL because there is no mapped
association for it. In the past, you either had to add the missing
association to your domain model or use a cross join in your JPQL query and hope that
the performance wouldn’t be too bad. Since Hibernate 5.1, you finally have a 3rd
option. You can join entities without a modeled association in HQL. And as you can see in
the code snippet, Hibernate uses an SQL-like syntax for it which makes it easy to use for
a lot of developers. Let’s jump into the IDE and check the generated
SQL statement for this query. So, here is the same query as I showed you
on the slide. It counts the reviews for each book and returns them together with the title.
I didn’t model the association between the Book and the Review entity in the domain model.
So, I need to define the JOIN clause within the query. Let’s run this test and take
a look at the generated SQL statement. Here you can see the SQL query and Hibernate
generated the expected SQL JOIN clause based on the provided JPQL JOIN clause. OK and now to the 5th and final feature of
this video: @Repeatable annotations. This is another small change that makes working
with Hibernate more comfortable. 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 NamedQuery annotation. @NamedQuery and a lot of other Hibernate annotations are now repeatable and can be assigned multiple times.
Isn’t that a lot cleaner and easier to read? OK, that’s it for today.
If you want to learn more about Hibernate, you should join the free Thoughts on Java
Library. It gives you free access to a lot of member-only content like a cheat for this
video and an ebook about the Java 8 support in the Hibernate 5. I’ll add the link to
it to the video description below. And if you like today’s video, please give
it a thumbs up and subscribe below. Bye!