-
Notifications
You must be signed in to change notification settings - Fork 4
Easily detect and fix N plus One SELECT with QuickPerf
N+1 select antipattern can lead to many JDBC roundtrips in production.
With QuickPerf you can easily detect and fix this antipattern from your tests.
Let's suppose that your project contains a Player JPA entity having a many to one association with a Team entity:
@ManyToOne(targetEntity = Team.class)
@JoinColumn(name = "team_id")
private Team team;
The fetch type is not specified. In JPA, the default fetching policy of @ManyToOne is EAGER.
Now, let's suppose that your application is executing the following a "FROM Player" Java Persistence query:
TypedQuery<Player> fromPlayer = entityManager.createQuery("FROM Player", Player.class);
List<Player> players = fromPlayer.getResultList();
The following SQL script was previously executed:
INSERT INTO TEAM VALUES (1, 'Manchester United');
INSERT INTO TEAM VALUES (2, 'AtlΓ©tico de Madrid');
INSERT INTO PLAYER VALUES (1, 'Paul', 'Pogba', 1);
INSERT INTO PLAYER VALUES (2, 'Antoine', 'Griezmann', 2);
The following SQL statements are then executed:
select
player0_.id as id1_0_,
player0_.firstName as firstNam2_0_,
player0_.lastName as lastName3_0_,
player0_.team_id as team_id4_0_
from
Player player0_"], Params:[()]
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
Team team0_
where
team0_.id=?"], Params:[(1)]
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
Team team0_
where
team0_.id=?"], Params:[(2)]
Because of the default EAGER fetch type, for each player a SELECT statement is executed to retrieve his team.
This N+1 select can be fixed by specifying a LAZY fetch type:
@ManyToOne(targetEntity = Team.class, fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
You can detect N+1 select by adding @ExpectSelect annotation on a test method to check the number of executed SELECT statements.
The outcome of an N+1 select is to have the same SELECT statements with different values. You can systematically detect this by configuring @DisableSameSelectTypesWithDifferentParams annotation with a global scope. In the previous examples, the outcome of the N+1 select was to have additional SELECT statements on Team table. These additional SELECT statements are the same apart from the id value of the Team table.
Hibernate code examples are available to play with these two ways of detecting N+1 selects:
π Β Hibernate JUnit 4 code example
π Β Hibernate JUnit 5 code example
π Β Hibernate TestNG code example
The use of QuickPerf to detect N+1 selects in a Spring Boot, a Quarkus or a Micronaut application is demonstrated in this repository.
QuickPerf automatically detects that the tested code uses Hibernate or Spring Data JPA. When a test is failing possibly due to an N+1 select, QuickPerf proposes ways to fix an N+1 select with Hibernate or Spring Data JPA:
Perhaps you are facing a N+1 select issue
* With Hibernate, you may fix it by using JOIN FETCH
or LEFT JOIN FETCH
or FetchType.LAZY
or ...
* With Spring Data JPA, you may fix it by adding
@EntityGraph(attributePaths = { "..." }) on repository method.
π Β Core
π Β JVM
π Β SQL
π Β Scopes
π Β Create an annotation
π Β JUnit 4
π Β JUnit 5
π Β TestNG
π Β Spring
π Β Detect and fix N+1 SELECT
π Β Maven performance
π Β Spring Boot - JUnit 4
π Β Spring Boot - JUnit 5
π Β Micronaut Data - JUnit 5
π Β Micronaut - Spring - JUnit 5
π Β Quarkus - JUnit 5
π Β FAQ
π Β QuickPerf code