230228 TIL

William Parker·2023년 2월 27일

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

  • An N + 1 problem means that N unintended queries are additionally executed when one query is executed.

When does it happen?

  • When calling an interface method using JPA Repository (when reading)

Who Who causes it?

  • Occurs when querying an entity that has a 1:N or N:1 relationship.

How and under what circumstances does it occur?

  • If JPA Fetch strategy retrieves data with EAGER strategy
  • When the JPA Fetch strategy re-queries the child entity that is an associative relationship after fetching the data with the LAZY strategy.

Why does it happen?

  • Because sub-entities are not fetched at once from the first query executed when finding with JPA Repository, and sub-entities are additionally searched when using them.
  • Because JPQL basically ignores the global fetch strategy and generates SQL only with JPQL.

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

  • Because all data is fetched in one query, the Paging API provided by JPA cannot be used (Pageable cannot be used)
  • Not available if there is more than one 1:N relationship
  • Unable to assign an alias (as) to the patch join target
  • Need to write cumbersome query statement

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.

  1. Remove duplicates by adding DISTINCT to JPQL
@Query("select DISTINCT o from Owner o join fetch o.pets")
List<Owner> findAllJoinFetch();
  1. Eliminate duplicates by declaring the OneToMany field type as a Set
@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.

profile
Developer who does not give up and keeps on going.

0개의 댓글