Spring JPA: when to use “Join Fetch”
Avoid N+1 Queries and retain the retrieval logic
One of the first and most common problems encountered by developers who are new to JPA / Hibernate is n+1 queries
. It’s because the code that is usually responsible for n+1 queries
doesn’t look weird but looks so simple. It makes other developers unaware of the performance issue and only discovers it upon having enough data/records to the database.
It might look something as simple as this: person.getAddresses();
For this article, I’ll be discussing a simple yet common scenario wherein using “Join Fetch” would be beneficial. I’ll also include two integration tests to compare the performance. But first, let’s read the definition from the documentation.
Hibernate Documentation
“A “fetch” join allows associations or collections of values to be initialized along with their parent objects using a single select. This is particularly useful in the case of a collection.”
Here’s a simple scenario to demonstrate the above definition:
Domain Entities / Models:
A Person Entity has a collection of Addresses
Now that we have our domains ready, create a PersonRepository
and then, query all records from Person
entity and simply display each address line
. Let’s start with the wrong approach which will lead to n+1 queries
.
N+1 Queries Results
Below is an integration test that demonstrates a code that leads to n+1 queries
.
With the gist of the integration test, I intentionally removed the imports and other annotations — so we can focus on the important parts.
Results from running the integration test above:
121515718 nanoseconds spent executing 51 JDBC statements;
Retrieved 50
records, executed 51
queries, and took about 121 ms
This might look negligible because we only have 50 records, but this clearly demonstrated the n+1 queries
scenario. Let’s now try using “Join Fetch” while preserving this way of displaying all address lines and compare the results using the same records.
JOIN FETCH Results
Below is the code for the PersonRepository
having a retrieveAll
method that utilizes JPQL to use “Join Fetch”.
The integration test would be almost similar from the previous one, the only difference is now we are using the retrieveAll
method that we just created.
With the gist of the integration test, I intentionally removed the imports and other annotations — so we can focus on the important parts.
Results from running the integration test above:
15411157 nanoseconds spent executing 1 JDBC statements
Retrieved 50
records, executed 1
query, and took about 15 ms
Conclusion
N+1 Queries
: executed 51
queries, and took about 121 ms
JOIN FETCH
: executed1
query, and took about 15 ms
The above comparison while may vary and limited to 50
records has a huge gap in performance and would probably have an exponential difference directly proportional to the number of records.
Using Join Fetch is one way to solve n+1 queries, while retaining the retrieval logic. Given that, there are other approaches e.g. Projections, and so on.
The full source code for this article is available on Github.
I hope you’ve enjoyed reading the article.
Happy Coding!