|

5 new features in Hibernate 5 every developer should know


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


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.

@Entity
public class Book {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = “id”, updatable = false, nullable = false)
	private Long id;
	
	private LocalDate publishingDate;

	…
}

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

16:15:33,545 DEBUG [org.hibernate.SQL] – insert into Book (price, publishingDate, title, version, id) values (?, ?, ?, ?, ?)
16:15:33,550 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] – binding parameter [1] as [DOUBLE] – [null]
16:15:33,551 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] – binding parameter [2] as [DATE] – [2017-04-04]
16:15:33,552 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] – binding parameter [3] as [VARCHAR] – [Hibernate Tips]
16:15:33,553 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] – binding parameter [4] as [INTEGER] – [0]
16:15:33,554 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] – binding parameter [5] as [BIGINT] – [1]

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.

Stream<Object[]> books = session.createNativeQuery("SELECT b.title, b.publishingDate FROM book b").stream();
books.map(b -> new BookValue((String)b[0], (Date)b[1]))
	.map(b -> b.getTitle() + " was published on " + b.getPublishingDate())
	.forEach(m -> log.info(m));

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.

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

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.

05:44:33,872 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 (
            ?,?,?
        )

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.

Query q = em.createQuery(“SELECT b.title, count(r.id) FROM Book b INNER JOIN Review r ON r.fkBook = b.id GROUP BY b.title”);
Object[] r = (Object[]) q.getSingleResult();
05:44:59,939 DEBUG [org.hibernate.SQL] - 
    select
        book0_.title as col_0_0_,
        count(review1_.id) as col_1_0_ 
    from
        Book book0_ 
    inner join
        Review review1_ 
            on (
                review1_.fkBook=book0_.id
            ) 
    group by
        book0_.title

@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.

@Entity
@NamedQueries({
	@NamedQuery(name = “Book.findByTitle”, query = “SELECT b FROM Book b WHERE b.title = :title”),
	@NamedQuery(name = “Book.findByPublishingDate”, query = “SELECT b FROM Book b WHERE b.publishingDate = :publishingDate”)
})
public class Book implements Serializable {
	…
}

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.

@Entity
@NamedQuery(name = “Hibernate5Book.findByTitle”, query = “SELECT b FROM Hibernate5Book b WHERE b.title = :title”)
@NamedQuery(name = “Hibernate5Book.findByPublishingDate”, query = “SELECT b FROM Hibernate5Book b WHERE b.publishingDate = :publishingDate”)
public class Hibernate5Book implements Serializable {
	…
}

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.

2 Comments

  1. Avatar photo Bernd Kursawe says:

    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/)

    1. Avatar photo Thorben Janssen says:

      Oh, …
      I referenced the wrong code snippet.

      Thanks Bernd!

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.