|

6 Hibernate features that I’m missing in JPA 2.1


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.


Note by the author:
I wrote this post in 2016. Since then, Oracle handed over the Java EE specification to the Eclipse Foundation, and the overall situation has massively improved!
Most of the features mentioned in this article have been added as part of JPA 2.2.


About 2 years ago, Oracle announced a maintenance release for JPA as part of Java EE 8. Lukas Jungmann presented his plans for it in his session at Java One 2015. We all know about the Java EE 8 situation in 2016 and JPA was also affected by it. The JPA tracker shows that there was no progress within the last year.

In the meantime, Hibernate added new, proprietary features. Here are 6 of them that I would like to see in the JPA specification.

Date and Time API

Java 8 introduced the Date and Time API, and there’s probably no Java developer who doesn’t prefer it over the old java.util.Date. Unfortunately, there is still no built-in support for it in JPA. The reason for that is simple; the latest JPA release is older than Java 8. The Date and Time API didn’t exist, when JPA 2.1 was released.

The next JPA release will hopefully support the Date and Time API classes as data types. The JPA spec tracker contains an old improvement request for it.

Until then, you have 2 options. You can either implement an AttributeConverter as shown in the following code snippet or use Hibernate’s proprietary Date and Time API support.

@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
	
    @Override
    public Date convertToDatabaseColumn(LocalDate locDate) {
    	return locDate == null ? null : Date.valueOf(locDate);
    }

    @Override
    public LocalDate convertToEntityAttribute(Date sqlDate) {
    	return sqlDate == null ? null : sqlDate.toLocalDate();
    }
}

Repeatable annotations

Repeatable annotations are another language feature added in Java 8. That might look like a small change, but that changes as soon as you’ve used some of Hibernate’s proprietary, repeatable annotations. It happens quite often that you want to add an annotation multiple times to the same entity. A typical example for that is the @NamedQuery annotation. The only way to do that with JPA 2.1 is to wrap it into a @NamedQueries annotation.

@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 {
	…
}

Hibernate 5.2 provides proprietary annotations with the same name. These are repeatable, and you don’t need the @NamedQueries annotation anymore.

@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 {
	…
}

I explained Hibernate’s repeatable annotations in more detail in Benefits of @Repeatable annotations in Hibernate 5.2. The short description in JPA_SPEC-115 indicates, that JPA 2.2 will offer a similar support of repeatable annotations.

Stream query results

Streams were also added in Java 8 and provide a comfortable and efficient way to process a list of Objects. As with the previous Java 8 features, Hibernate 5 already provides query results as a Stream and it’s a requested feature for JPA 2.2.

You might say that you can already get a query result as a Stream with JPA 2.1. That’s basically right because you can get the result as a List and call the stream() method to get a Stream representation of it.

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

This approach gives you a Stream of the selected result set. But Hibernate offers a better solution for it. The getResultList() method requires Hibernate to fetch all records of the result set and to put them into the List. If you use a Stream to process the records, you don’t need to fetch all of them at the beginning. You process them one by one anyways. Hibernate, therefore, uses it’s proprietary ScrollableResultSet to scroll through the result set and fetch the records in small batches.

Stream<Book> 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));

Natural IDs

Good support for natural IDs is another feature I’m missing in JPA. Most domain models don’t use them as primary keys, but that doesn’t mean that the business logic doesn’t need them. A lot of use cases rely on natural IDs instead of the generated surrogate keys.

With JPA, you need to write custom queries to retrieve entities by their natural ID.

Hibernate offers a proprietary API that not only provides some convenience features. It also utilizes the existing caches.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Session session = em.unwrap(Session.class);

Book b = session.byNaturalId(Book.class).using(Book_.isbn.getName(), “978-0321356680”).load();

I explain Hibernate’s natural ID support in more details in @NaturalId – A good way to persist natural IDs with Hibernate?

Ad hoc join of unrelated entities

When your domain model defines relationships with lots of entities, you most likely don’t want to manage them with JPA or Hibernate. A typical example for that is the relationship between an entity that defines a product and an entity that represents a sold instances of it. The risk is too high that someone just calls a getter method and Hibernate loads a few thousand entities from the database. If that line of code sneaks onto your production system, it will most likely cause a few angry customer complaints.

The common solution is to not model this relationship between your entities. That prevents everyone from calling the getter method, but with JPA, it also prevents you from joining these entities in a JPQL query. You either have to use a cross join or a native query.

Hibernate also supports ad hoc joins of unrelated entities, like you can see in the following code snippet.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
	
List<Object[]> results = em.createQuery("SELECT p.firstName, p.lastName, n.phoneNumber FROM Person p JOIN PhoneBookEntry n ON p.firstName = n.firstName AND p.lastName = n.lastName").getResultList();

for (Object[] result : results) {
	log.info(result[0] + " " + result[1] + " - " + result[2]);
}

em.getTransaction().commit();
em.close();

Load by multiple ID

Loading multiple entities by their primary key is another feature I like in Hibernate. You can see an example of it in the following code snippet.

MultiIdentifierLoadAccess<PersonEntity> multiLoadAccess = session.byMultipleIds(PersonEntity.class);
List<PersonEntity> persons = multiLoadAccess.multiLoad(1L, 2L, 3L);

Hibernate splits the array of primary keys into one or more chunks and provides them as parameters to an IN statement.

14:32:57,602 DEBUG SQL:92 – select personenti0_.id as id1_0_0_, personenti0_.firstName as firstNam2_0_0_, personenti0_.lastName as lastName3_0_0_ from Person personenti0_ where personenti0_.id in (?,?,?)

That is obviously just a convenience feature, but it makes it a lot easier to load multiple entities by their primary key efficiently.

Summary

Most of the features I like to see in the JPA specification are convenience features. The main reason is that the specification already provides good solutions for most common use cases. One exception is, of course, the missing Java 8 support and I’m also missing a good solution for multi-tenancy. But besides that, there are not a lot of things missing and multi-tenancy will (hopefully) be addressed on Java EE level and involve multiple specifications.

Does that mean that we don’t need the promised maintenance release?

No! Especially the Java 8 support is overdue and there were already a lot of discussions about multi-tenancy. If JPA shall stay relevant, these features need to be added. Until then, we need to use proprietary implementation features. That is fine as long as you don’t need to switch your JPA provider.

If you want to learn more about these and other advanced Hibernate features, you should join my Advanced Hibernate Online Training.

7 Comments

  1. Excellent article as usual!

    Just wanted to point out that, in my opinion, load-by-multiple-ids is definitely more than “just a convenience” as it can have a major performance impact (which is why it was added in the first place).

    1. Avatar photo Thorben Janssen says:

      Thank you Steve!
      The reason why I see the load-by-multiple-ids as a convenience feature is that you could implement it yourself.

      You could write a JPQL query with an IN statement in the WHERE clause like Hibernate does. You would, of course, need to implement the batching yourself and you would probably loose the session synchronization. But you could implement a similar behavior.
      I discussed that in more detail in this post: //thorben-janssen.com/fetch-multiple-entities-id-hibernate/

  2. Always a good read these! Keep up the good work.

  3. Avatar photo Hatem Jaber says:

    Thanks Thorben, these are really excellent tips!

    1. Avatar photo Thorben Janssen says:

      Thanks!
      I’m happy to hear that you find them helpful

  4. Avatar photo Romeo Frenksen says:

    Thanks for this nice summary.

    1. Avatar photo Thorben Janssen says:

      You’re welcome 🙂
      Thanks for reading it.

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.