What is the JPA N+1?
A common problem with JPA is the N + 1 problem.
Since N + 1 problems can have a huge impact on performance, what is an N + 1 problem and under what circumstances does it occur? We want to find out how to solve it.
What is JPA N+1 problem
When does it happen?
Who Who causes it?
How and under what circumstances does it occur?
Why does it happen?
If EAGER (immediate loading)
1. Query data through SQL created in JPQL
2. Afterwards, JPA uses a fetch strategy to additionally search sub-entities related to the data.
3. N + 1 problem occurred in step 2
If LAZY (lazy loading)
1. Query data through SQL created in JPQL
2. JPA has a fetch strategy, but no additional lookup because of lazy loading
3. However, when working with sub-entities, additional lookups occur, resulting in N + 1 problems.
How to solve N+1 problem
There are many ways to solve it, but we will look at two methods, FetchJoin and EntityGraph.
Fetch Join
The reason why N+1 itself occurs is that only one table is queried and the other linked table is queried separately.
If you can JOIN the two tables in advance and get all the data at once, you won't have an N+1 problem in the first place.
The solution that came out like that is the FetchJoin method.
You can write your own query that JOINs the two tables.
Directly specify JPQL as follows.
@Query("select DISTINCT o from Owner o join fetch o.pets")
List<Owner> findAllJoinFetch();
If you look at the result, you can see that the query occurs only once, and the owner and pet data are joined (Inner Join) beforehand.
Disadvantages of Fetch Join
FetchJoin has a Cartesian product in common, which can cause duplication.
※ Cartesian Product: When a valid join condition is not written between two tables, all data for the table are combined and the result value is returned as much as the number of rows existing in the table is multiplied.
@Query("select DISTINCT o from Owner o join fetch o.pets")
List<Owner> findAllJoinFetch();
@OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
private Set<Pet> pets = new LinkedHashSet<>();
(Set has a feature that order is not guaranteed, but if you need order guarantee, use LinkedHashSet.)
Avoid Cartesian Products
Cross Join, where Cartesian product occurs, is a problem that occurs in the expression of the query, not because of the JPA function.
The condition for cross join is simple.
When a clear Join rule is not given when the Join command is executed,
When there is no on clause after join, db should export the result of combining the two tables, and since there is no condition, the number of all cases is output as M * N.
JPA interprets the code sent by the user and assembles the optimal SQL statement.
At this time, it may or may not occur depending on how clearly the code reveals the relationship.
The functions of Fetch Join and @EntityGraph are not 'create a cross join' or 'create an inner join',
'Bring the association data together EAGER'.
To derive an optimized query in a specific direction (usually an inner join) from the JPA framework,
All you need to do is properly convey the relationships and situations that the framework can understand to your code.
At this time, Fetch Join, FetchType.EAGER, @EntityGraph, Querydsl, etc. help induce optimized query.