Whether we like it or not, we use ORMs a lot in our applications. We can love and hate them at the same time. In one hand, they help a lot to write an Object-Oriented code only. In other hand, their complexity causes the issue of bad usage, often due to other frameworks that hide ORMs behind another abstraction (for example, Spring Data framework) or by wrong ORMs usage themselves.

A few months ago, I encountered a really interesting situation regarding ORM usage in one of the Spring-based projects.

Table of contents:

Problem definition

First of all, let me introduce the Entity Graph feature which comes from JPA 2.1 specification. This concept allows you to mark and group some collections inside the entity, to fetch them in a very flexible way at runtime.

As you probably know, JPA 2.0 introduced two types of fetching strategies:

  1. FetchType.LAZY
  2. FetchType.EAGER

The implementation is very simple and allows you to define the fetch type strategy on each collection inside the entity in a static way. In other words, you cannot switch between these two strategies at runtime and choose proper behavior depending on different use case scenarios.

The new concept, called Entity Graph, allows for creating meta-definitions by using dedicated annotation. The @NamedEntityGraph annotation allows for listing attributes to include when taking data from a relational database (see example below):

@NamedEntityGraphs({
            @NamedEntityGraph(
        name = "ContractWithGuaranteesOnly,
        attributeNodes = {
                @NamedAttributeNode(“attribute1”),
                @NamedAttributeNode(“attribute2”),
                @NamedAttributeNode(“guarantees”)
        },
            @NamedEntityGraph(
        name = "ContractWithTermsAndConditionsOnly,
        attributeNodes = {
                @NamedAttributeNode(“attribute1”),
                @NamedAttributeNode(“attribute2”),
                @NamedAttributeNode(“termsConditions”)
        }
)
 
})
public class Contract {
 
            @OneToMany
            private Set<Guarantee> guarantees;
 
            @OneToMany 
            private Set<TermsConditions> termsConditions;
            //….
 
}

In this example, the @NamedAttributeNode annotation has been used to define related entities to be loaded when we use a specific @NamedEntityGraph. Looks awesome, doesn’t it? However, as with any tool, it can be a double-edged sword that can easily kill you or at least hurt you. In my specific case, one of the developers decided to define the entity graph, called Contract.getFull. To make matters worse, the Contract entity had more than 20 collections defined there (it is a legacy system and please don’t ask why this entity is so big. I leave it without comment). Anyway, the situation was not good, because this EntityGraph was fetched in many places causing really huge performance issues and consuming a lot of memory.

Problem description

When the @NamedEntityGraph is used, the LEFT OUTER JOIN query is used under the hood. In our specific example it looked like this:

select * from contract
            left outer join terms_conditions termscondi1_ on contract0_.terms_conditions_id=termscondi1_.id 
            left outer join guarantee guarantees11_ on contract0_.id=guarantees11_.contract_id
            //+22 similar outer joins
where SOME_NOT_IMPORTANT_CONDITIONS

This query returns the Cartesian product in order to build ONE simple Contract entity. In our specific case, it was around 200 000 records from DB which have to be mapped by ORM to one single object. This amount of records caused the application to freeze (ORM is not able to process this amount of data). The issue is officially created on the Hibernate ORM Jira:https://hibernate.atlassian.net/browse/HHH-12897

Problem solution

Right now, there is no permanent solution for this case. Of course, I fully understand that the problem is the result of bad design and an overly complex entity, and how the framework has been used. In my specific case, I fully removed the @NamedEntityGraph which retrieves all the collections. Instead of this, I put in the good old FetchType.LAZY mapping as a workaround. It produces sub-queries instead of a huge LEFT OUTER JOIN construction. One side effect is to allow Lazy Loading outside the transaction. In the Spring application, the following property should be set:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

I imagine that this solution is not a silver bullet and has a lot of other side effects, but in my particular situation, it works. Instead of waiting 1 minute to fetch data, it is now less than 1 second. Good enough, right?! 🙂

In order to fully solve the issue, deep code refactoring would be required, and the entity would have to be decomposed into smaller pieces.

Read also: Setting Default Spring Profile for Tests with Override Option

5/5 - (10 votes)