Hi,
I’m Thorben Janssen from thoughts-on-java.org. Today I will talk about 5 common Hibernate mistakes that cause dozens of unexpected queries and horrible headaches for most developers. If there is one thing that’s often criticized about JPA and Hibernate, it’s that it sometimes executes more queries than you expected. And I’m not talking about 1 or 2 additional queries. I’m talking about dozens or even hundreds of them. The worst thing about it is that you don’t recognize that problem if you don’t configure your logging correctly or activate Hibernate’s
statistics component. But while this is a problem that often occurs with JPA and Hibernate, you shouldn’t solely blame the framework for it. In most situations, these queries are caused by small mistakes which you could have easily prevented. So, it’s up to you and me, the developer, to avoid these mistakes and use JPA and Hibernate efficiently. Let me show you the 5 most common mistakes that cause Hibernate to execute dozens or even hundreds of queries and how you can avoid them. But before we proceed, please subscribe and hit the bell icon to stay up to date on Hibernate and JPA. The initialization of lazily fetched associations is one of the most common reasons for Hibernate to generate additional queries. If you have been using Hibernate for a while, you probably experienced this problem yourself. It’s often called the n+1 select issue. Whenever you access an uninitialized, lazily fetched association, Hibernate executes a query that loads all elements of this association from the database. This code snippet shows a typical example that forces Hibernate to trigger an additional query to get the associated Book entities for each Author entity. When you run this code and activate the logging of SQL statements, you can see that Hibernate executes an additional query for each Author to fetch the associated Book entities. If you’re only fetching a few Author entities, this is not a big issue. You might not even recognize it because Hibernate and your database process the additional queries in a few milliseconds. But that changes dramatically if the Book entity becomes more complex or if you select several hundred Author entities. you can easily avoid this problem by initializing the associations you want to use while retrieve an entity from the database. JPA and Hibernate offer several options to do that. The easiest one is a JOIN FETCH or LEFT JOIN FETCH expression which you can use within your JPQL or Criteria Query. You can use it in the same way as a simple JOIN expression. It tells Hibernate not only to join the association within the query but also to fetch all elements of the association. Let’s improve the previous example and fetch the associated Book entities with a LEFT JOIN FETCH clause. As you can see in the code snippet, the change is relatively simple. You just need to add the LEFT JOIN FETCH expression to the FROM clause. This small change in your JPQL query has a huge impact on the generated SQL statements. Hibernate now executes only 1 query instead of multiple ones and it also changed the SELECT clause of the query to include all columns mapped by the Book entity. As you can see, it requires almost no effort
to avoid these additional queries. Let’s take a look at the 2nd common mistake. Not only the initialization of lazily fetched
associations can cause lots of unexpected queries. That’s also the case if you use FetchType.EAGER. It forces Hibernate to initialize the association as soon as it loads the entity. So, it doesn’t even matter if you use the association or not. Hibernate will fetch it anyways. That makes it one of the most common performance
pitfalls. And before you tell me that you never declare any eager fetching in your application, please check your to-one associations. Unfortunately, JPA defines eager fetching as the default behavior for these associations. OK, so how do you avoid these problems? You should use FetchType.LAZY for all of your associations. It’s the default for all to-many associations, so you don’t need to declare it for them explicitly. But you need to do that for all to-one associations. You can specify the FetchType with the fetch attribute of the OneToOne or ManyToOne annotation. OK, let’s continue with common mistake number 3. Cascading provides a very comfortable option
to remove all child entities when you delete their parent entity. In the example of this video, you could, for example, use it to remove all Reviews of a Book, when you delete the Book entity. Unfortunately, the removal of the associated entities isn’t very efficient and can create serious side effects. I, therefore, recommend to NOT use CascadeType.Remove in your application. Let’s take a look at it anyways so that you know how it works and what you should do instead. The only thing you need to do to activate the cascading of remove operations is to set the cascade attribute on the OneToMany annotation to CascadeType.Remove. When you now remove a Book entity, Hibernate cascades the operation to the associated Review entities. Do you see all the DELETE statements in the log? Yes, that’s right. Hibernate loads all associated entities and removes each of them with its own SQL DELETE statement. It needs to do that because it transitions the lifecycle state of each entity from managed to removed. That provides the advantage that all lifecycle events get triggered and caches are updated. If you don’t use any lifecycle callbacks or EntityListeners and don’t use any frameworks that use them, like Hibernate Envers, you can use a JPQL, Criteria or native SQL query to remove all child entities with one bulk operation. This is more efficient but doesn’t trigger any lifecycle events. The following code shows a JPQL query that removes all Review entities associated with a given Book entity. As you can see, the syntax is pretty similar to SQL. Hibernate doesn’t know which entities this operation removes. So, it can’t remove any of the deleted entities from the persistence context. You either need to be sure that you didn’t fetch any of the removed entities before you executed the bulk operation or you need to call the flush and clear methods on the EntityManager before you execute the JPQL query. This tells Hibernate to write all pending changes to the database and to detach all entities from the persistence context before you execute the remove operation. So, you can be sure that your persistence context doesn’t contain any outdated entities. Let’s move on to common mistake number 4. Another common but not that well-known mistake that causes Hibernate to execute lots of addition SQL statements is using the wrong data type to model many-to-many associations. If you map the association to an attribute of type java.util.List, Hibernate deletes all existing association records and then inserts a new one for each managed association whenever you add an element to or remove one from the association. Let’s take a look at a simple example. I modeled a many-to-many association between my Author and Book entity. I mapped this association to the List authors attribute on the Book entity. When I now add or remove a new Author to the association, Hibernate deletes all records from the book_author relationship table which are associated with the Book before it inserts a new record for each managed association. That’s obviously not the most efficient approach and probably not what you expected. You can easily fix that by changing the data type from List to Set. When you now execute the same code as before, Hibernate doesn’t remove the existing records in the book_author table and just inserts
the new one. That’s a simple fix, isn’t it. The last mistake I want to talk about in
this video slows down use cases that update or remove multiple database records in the same table. With SQL, you would create 1 SQL statement that updates or removes all affected records. That would also be the most efficient approach with JPA and Hibernate. But that’s not what Hibernate does if you
update or remove multiple entities. Hibernate creates an update or remove statement for each entity. So, if you want to remove 100 entities, Hibernate creates and executes 100 SQL DELETE statements. Lets have a look at examples. Executing that many SQL statements is obviously not a very efficient approach. It’s much better to implement a bulk operation as a native, JPQL or Criteria query. This is the same approach as I showed you as the solution to mistake 3. You first need to flush and clear your persistence context before you execute a query that removes all entities that fulfill the defined criteria. As you can see in the log messages, Hibernate now removes the entities with a single statement instead of multiple ones. As you’ve seen, the most common mistakes are not only easy to make, they are also very easy to avoid: When you’re defining your associations, you should prefer FetchType.LAZY and map many-to-many associations to a java.util.Set. If your use case uses a lazily fetched association, you should initialize it within the query that loads the entity, for example with a JOIN FETCH expression. Cascading and updating or removing multiple entities require more SQL statements than you might expect. It’s often better to implement a bulk operation as a native, JPQL or Criteria query. By following these recommendations, you will avoid the most common mistakes that cause Hibernate to execute lots of unexpected queries. If you want to learn more about Hibernate
performance optimizations, you should join the waiting list of my Hibernate Performance Tuning Online Training. I will reopen it to enroll a new class very soon. 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 using native queries with JPA and Hibernate. 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