7 Tips to boost your Hibernate performance

By Thorben Janssen

Association Mapping, Criteria API, Jpql, Mapping, Query

Implementing your persistence tier with Hibernate is quite easy and as the most popular JPA implementation also the way to go for Java EE applications. But what starts as nice and easy often turns out to be an issue as soon as the requirements get more challenging. You need to know more than just the basics of Hibernate and JPA to create an application that performs well on a huge database or under high load.

But don’t worry, the most common reasons for performance issues are not that difficult to fix. We will have a look at 7 Tips that will help you to speed up your persistence tier and you can download them as a free Hibernate Performance Tuning cheat sheet. If you like to learn more about Hibernate performance tuning, you should have a look at my online training.

1. Find performance issues with Hibernate Statistics

Finding the performance issues as early as possible is always the most important part. The main issue with that is, that most of the performance issues are hardly visible on a small test system. They are caused by some small inefficiencies which are not visible, if you test with a small database and only one parallel user on your local test system. That changes dramatically as soon as they hit production.

While the performance issues are difficult to find on your test system, you can still see the inefficiencies, if you have a look at some internal statistics. One way to do this is to activate Hibernate Statistics by setting the system property hibernate.generate_statistics to true and log level for org.hibernate.stat to DEBUG. Hibernate will then collect some internal statistics about each session like the number of performed queries and the time spent for them or number of cache hits and misses.

2015-03-03 20:28:52,484 DEBUG [org.hibernate.stat.internal.ConcurrentStatisticsImpl] (default task-1) HHH000117: HQL: Select p From Product p, time: 0ms, rows: 10
2015-03-03 20:28:52,484 INFO  [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] (default task-1) Session Metrics {
    8728028 nanoseconds spent acquiring 12 JDBC connections;
    295527 nanoseconds spent releasing 12 JDBC connections;
    12014439 nanoseconds spent preparing 21 JDBC statements;
    5622686 nanoseconds spent executing 21 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    403863 nanoseconds spent executing 1 flushes (flushing a total of 10 entities and 0 collections);
    25529864 nanoseconds spent executing 1 partial-flushes (flushing a total of 10 entities and 10 collections)
}

You can use these information during development and check them against your expectations. By doing this regularly, you can avoid the most common issues caused by slow queries, too many queries and missing cache usage. And keep in mind that you are working with a small test database. 5 or 10 additional queries during your test might be several hundreds or thousands, if you switch to the bigger production database.

2. Improve slow queries

Slow queries are not a real JPA or Hibernate issue. This kind of performance problems occurs with every framework, even with plain SQL over JDBC and needs to be analyzed and fixed on the SQL and database level. If you do this, you will quit often recognize that you can not do these more complex or optimized SQL queries with JPQL or the Criteria API. In these cases you need to use a native query to perform a native SQL statement, in which you can use all SQL and proprietary database features. But this also has a drawback. You get an Object[] instead of the strongly typed results that you get from JPQL. You can map these Object[] either programatically or via @SqlResultSetMapping annotations:

3. Choose the right FetchType

Another common issue is the usage of the wrong FetchType. It is specified in the entity mapping and defines when a relationship will be loaded. Using the wrong FetchType can result in a huge number of queries that are performed to load the required entities.

@ManyToMany( mappedBy="authors", fetch=FetchType.LAZY)

The main problem of the FetchType definition is, that you can only define one FetchType for a relationship. This will be used every time an entity gets fetched from the database. So you either have only queries that have to request the same relationships (which I doubt) or you are not able to define the best FetchType for all of them. So you need to choose the best default FetchType which in most of the cases is FetchType.LAZY.

4. Use query specific fetching

As I explained in Tip 3, you are not able to define the optimal FetchType for all queries in the entity mapping. The good thing is, that you don’t need to do that. You only need to define the ideal default behavior, which in most of the cases is FetchType.LAZY.

You can then define eager fetching for the specific queries for which you really need it. This can be done in different ways:

  • You can either do this as part of your JPQL statement by using FETCH JOIN instead of JOIN. The additional FETCH keyword tells Hibernate to not only join the two entities within the query but to also fetch the related entities from the database.
    SELECT DISTINCT a FROM Author a JOIN FETCH a.books b
  • Another option is to use a @NamedEntityGraph. This is one of the new features introduced in JPA 2.1 and allows you to define a graph of entities that shall be fetched from the database.
    @NamedEntityGraph(name = "graph.AuthorBooksReviews", 
    attributeNodes = @NamedAttributeNode(value = "books"))

    If you combine above’s entity graph with a query that selects an Author entity, the entity manager will also fetch the books relationship from the database. You can find a more detailed description about @NamedEntityGraphs and how to use them to define more complex graphs in JPA 2.1 Entity Graph – Part 1: Named entity graphs.

  • If you need a more dynamic way to define your entity graph, you can also do this via a Java API. The following code snippet defines the same graph as the previously described annotations.
    EntityGraph graph = this.em.createEntityGraph(Author.class);
    Subgraph<Book> bookSubGraph = graph.addSubgraph(Author_.books);

5. Let the database handle data heavy operations

OK, this is a tip that most Java developers don’t really like because it moves parts of the business logic from the business tier (implemented in Java) into the database. And don’t get me wrong, there are good reasons to choose Java to implement the business logic and a database to store the data. But you also have to consider that a database is very efficient in handling huge datasets. It can therefore be a good idea to move not too complex and very data heavy operations into the database. There are multiple ways to do that. You can perform simple operations in JPQL and native SQL queries. And if you need more complex operations, you can call stored procedures via @NamedStoredProcedureQuery or the corresponding Java API. The following code snippet shows a @NamedStoredProcedure definition for the getBooks stored procedure. This procedure returns a REF_CURSOR which can be used to iterate through the returned data set.

@NamedStoredProcedureQuery( 
  name = "getBooks", 
  procedureName = "get_books", 
  resultClasses = Book.class,
  parameters = { @StoredProcedureParameter(mode = ParameterMode.REF_CURSOR, type = void.class) }
)

6. Use caches to avoid reading the same data multiple times

Modular application design and parallel user sessions often result in reading the same data multiple times. It is quite obvious that this an overhead that you should try to avoid. One way to do this is to cache the data that it often read and not changed to often.

As you can see below, Hibernate offers 3 different caches that can be combined with each other.

  • The 1st level cache is activated by default and caches all entities that were used within the current session.
  • The session independent 2nd level cache also stores entities but needs to be activated by setting the shared-cache-mode property in the persistence.xml. The caching of specific entities can be activated by adding the javax.persistence.Cacheable or the org.hibernate.annotations.Cache annotation to the entity.
  • The query cache is the only one which does not store entities. It caches query results and contains only entity references and scalar values. You need to activate the cache by setting the hibernate.cache.use_query_cache property in the persistence.xml file and set the cacheable property on the Query.

7. Perform updates and deletes in bulks

Updating or deleting one entity after the other feels quite natural in Java but it is also very inefficient. Hibernate creates one SQL query for each entity that was updated or deleted. The better approach would be to perform these operations in bulks by creating update or delete statements that affect multiple records at once.

This can be done via JPQL or SQL statements or by using the CriteriaUpdate and CriteriaDelete operations that were added in JPA 2.1. The following code snippet shows an example for a CriteriaUpdate statement. As you can see, it is used in a similar way as the already known CriteriaQuery statements.

CriteriaBuilder cb = this.em.getCriteriaBuilder();
  
// create update
CriteriaUpdate<Order> update = cb.createCriteriaUpdate(Order.class);

// set the root class
Root e = update.from(Order.class);

// set update and where clause
update.set("amount", newAmount);
update.where(cb.greaterThanOrEqualTo(e.get("amount"), oldAmount));

// perform update
this.em.createQuery(update).executeUpdate();

Conclusion

As you have seen, there are several Hibernate features that can be used to avoid inefficiencies and to boost the performance of your application. In my experience, the most important ones are the Hibernate statistics which allow you to find these inefficiencies and the definition of the right FetchType in the entity mapping and the query.

You can get more information about these and other performance tuning tips in my Hibernate Performance Tuning Online Training.

Before you leave, make sure to download your Hibernate Performance Tuning cheat sheet which summarizes the tips of this post.


Tags

Association Mapping, Criteria API, Jpql, 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. That’s right. The batch update/delete operations are independent of the entities. So everything that works directly on the entities, like @(Post|Pre)(Update|Remove) triggers or entity caching, are not affected by the update/delete operation.

    1. Thank you, Vinod.
      I send you an email with the link to the pdf and it was also in the mail about this post, which I send this morning.

      Please let me know, if there are any further issues.

      Regards,
      Thorben

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