Hi,
I’m Thorben Janssen from thoughts-on-java.org. Do you think your application could be faster if you would just solve your Hibernate problems? Then I have good news for you! I fixed performance problems in a lot of applications, and most of them were caused by the same set of mistakes. And it gets even better, most of them are easy to fix. So, it probably doesn’t take much to improve your application. Here is a list of the 10 most common mistakes
that cause Hibernate performance problems and how you can fix them. Mistake number One: Use eager fetching The implications of FetchType.EAGER have been discussed for years, and there are lots of posts explaining it in great details. I wrote one of them myself. But unfortunately, it’s still one of the 2 most common reasons for performance problems. The FetchType defines when Hibernate initializes an association. You can specify with the fetch attribute of the OneToMany, ManyToOne, ManyToMany and OneToOne annotation. Hibernate loads eagerly fetched associations when it loads an entity For example, when Hibernate loads a Author entity, it also fetches the associated Book entity. That requires an additional query for each Author and often accounts for dozens or even hundreds of additional queries. This approach is very inefficient and it gets even worse when you consider that Hibernate does that whether or not you will use the association. Better use FetchType.LAZY instead. It delays the initialization of the relationship until you use it in your business code. That avoids a lot of unnecessary queries and improves the performance of your application. Luckily, the JPA specification defines the FetchType.LAZY as the default for all to-many associations. So, you just have to make sure that you don’t change it. But unfortunately, that’s not the case for to-one relationships. And that gets us to mistake number two: Ignore the Default FetchType of To-One Associations The next thing you need to do to prevent eager fetching is to change the default FetchType for all to-one associations. Unfortunately, these relationships are eagerly fetched by default. In some use cases, that’s not a big issue
because you’re just loading one additional database record. But that quickly adds up if you’re loading multiple entities and each of them specify a few of these associations. So, better use the fetch attribute of the ManyToOne and OneToOne annotation to set the FetchType to LAZY. Mistake number three: Don’t Initialize Required Associations When you use FetchType.LAZY for all of your associations to avoid mistake one and two, you will find several n+1 select issues in your code. This problem occurs when Hibernate performs 1 query to select n entities and then has to perform an additional query for each of them to initialize a lazily fetched association Hibernate fetches lazy relationships transparently so that this kind of problem is hard to find in your code. You’re just calling the getter method of your association and you most likely don’t expect Hibernate to perform any additional query. The n+1 select issues get a lot easier to find if you use a development configuration that activates Hibernate’s statistics component and monitors the number of executed SQL statements. As you can see here, the JPQL query and the call of the getBooks method for each of the 12 selected Author entities, caused 13 queries. That is a lot more than most developers expect when they implement such a simple code snippet. You can easily avoid that, when you tell Hibernate to initialize the required association. There are several different ways to do that. The easiest one is to add a JOIN FETCH statement to your FROM clause. Mistake number four: Select More Records Than You Need I’m sure you’re not surprised when I tell you that selecting too many records slows down your application. But I still see this problem quite often, when I analyze an application in one of my consulting calls. One of the reasons might be that JPQL doesn’t support the OFFSET and LIMIT keywords you use in your SQL query. It might seem like you can’t limit the number of records retrieved within a query. But you can, of course, You just need to set this information on the Query and the TypedQuery interface and not in the JPQL statement. You can see a simple example here on the slide. I first order the selected Author entities by their id and then tell Hibernate to retrieve the first 5 entities. Mistake number five: Don’t use Bind Parameters Bind parameters are simple placeholders in your query and provide a lot of benefits that are not performance related: First of all, they are extremely easy to use. Hibernate performs required conversions automatically. Second Hibernate escapes Strings automatically which prevents SQL injection vulnerabilities. And they also help you to implement a high-performance
application. Most applications execute a lot of the same queries which just use a different set of parameter values in the WHERE clause. Bind parameters allow Hibernate and your database to identify and optimize these queries. You can use named bind parameters in your JPQL statements. Each named parameter starts with a colon “:” followed by its name. After you’ve defined a bind parameter in your query, you need to call the setParameter method on the Query interface to set the bind parameter value. Mistake number Six: Perform all Logic in Your Business Code For us as Java developers, it feels natural to implement all logic in your business layer. We can use the language, libraries, and tools that we know best. And we call that layer the business layer for a reason, right? But sometimes, the database is the better place to implement logic that operates on a lot of data. You can do that by calling a function in your JPQL or SQL query or with a stored procedure. Let’s take a quick look at how you can call a function in your JPQL query. want to dive deeper into this topic, you can read my posts about stored procedures. You can use standard functions in your JPQL queries in the same way as you call them in your SQL query. just reference the name of the function followed by an opening bracket, an optional list of parameters and a closing bracket. Mistake number Seven: Call the flush Method Without a Reason This is another popular mistake. I’ve seen it quite often, that developers call the flush of the EntityManager after they’ve persisted a new entity or updated an existing one. That forces Hibernate to perform a dirty check on all managed entities and to create and execute SQL statements for all pending insert, update or delete operations. That slows down your application because it prevents Hibernate from using several internal optimizations. Hibernate stores all managed entities in the persistence context and tries to delay the execution of write operations as long as possible. That allows Hibernate to combine multiple update operations on the same entity into 1 SQL UPDATE statement, to bundle multiple identical SQL statements via JDBC batching and to avoid the execution of duplicate SQL statements that return an entity which you already used in your current Session. As a rule of thumb, you should avoid any calls of the flush method. One of the rare exceptions are JPQL bulk operations which I explain in mistake 9. Mistake number Eight: Use Hibernate for Everything Hibernate’s object-relational mapping and various performance optimizations make the implementation of most CRUD use cases very easy and efficient. That makes Hibernate a popular and good choice for a lot of projects. But that doesn’t mean that it’s a good solution for all kinds of projects. I discussed that in great detail in one of
my previous posts and videos. JPA and Hibernate provide great support for most of the standard CRUD use cases which create, read or update a few database records. For these use cases, the object relational mapping provides a huge boost to your productivity and Hibernate’s
internal optimizations provide a great performance. But that changes, when you need to perform very complex queries, implement analysis or reporting use cases or perform write operations on a huge number of records. All of these situations are not a good fit for JPA’s and Hibernate’s query capabilities and the lifecycle based entity management. You can still use Hibernate if these use cases are just a small part of your application. But in general, you should take a look at other frameworks, like jOOQ or Querydsl, which are closer to SQL and avoid any object relational mappings. Mistake number Nine: Update or Delete Huge Lists of Entities One by One When you look at your Java code, it feels totally fine to update or remove one entity after the other. That’s the way we work with objects, right? That might be the standard way to handle Java objects but it’s not a good approach if you need to update a huge list of database records. In SQL, you would just define one UPDATE or DELETE statement that affects multiple records at once. Databases handle these operations very efficiently. Unfortunately, that’s not that easy with JPA and Hibernate. Each entity has its own lifecycle and if you want to update or remove multiple of them, you first need to load them from the database. Then you can perform your operations on each of the entities and Hibernate will generate the required SQL UPDATE or DELETE statement for each of them. So, instead of updating 1000 database records with just 1 statement, Hibernate will perform at least 1001 statements. It should be obvious that it will take more time to execute 1001 statements instead of just 1. Luckily, you can do the same with JPA and Hibernate with a JPQL, native SQL or Criteria query. But it has a few side-effects that you should be aware of. You perform the update or delete
operation in your database without using your entities. That provides the better performance but it also ignores the entity lifecycle and Hibernate can’t update any caches. I explained that in great details in the video- How to use native queries to perform bulk updates. To make it short, you shouldn’t use any lifecycle listeners and you need to call the flush and clear methods on your EntityManager before you execute a bulk update. The flush method will force Hibernate to write all pending changes to the database before the clear method detaches all entities from the current persistence context. Mistake number Ten : Use Entities for Read-Only Operations JPA and Hibernate support several different projections. You should make use of that if you want to optimize your application for performance. The most obvious reason is that you should only select the data that you need in your use case. But that’s not the only reason. As I showed in a recent test, DTO projections are a lot faster than entities, if you read the same database columns. Using a constructor expression in your SELECT clause instead of an entity is just a small change. But in my test, the DTO projection was 40% faster than entities. And even so, the exact numbers depend on your use case, you shouldn’t pass on such an easy and efficient way to improve the performance. 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 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