Card 01/ 06
The N+1 Query Problem
One findAll() for twenty rides can send twenty-one queries to the database.
The N+1 query problem is a pattern where loading a list of N records sends one query for the list and then one more query for each record in it. It is easy to write by accident with JPA, because the extra queries are hidden behind ordinary getters.
A ride-sharing service lists the name of the driver for each of today's rides. The code looks like one database call:
@Transactional(readOnly = true)
List<String> driverNamesForEveryRide() {
List<Ride> rides = rideRepo.findAll(); // query #1
return rides.stream()
.map(r -> r.getDriver().getName()) // one more query, per ride
.toList();
}With twenty rides, Hibernate's statistics count twenty-one queries:
20 rides, 1 findAll() call, total prepared statements executed: 21The first query is findAll(), which loads the rides. Each ride's driver is loaded lazily, which means JPA does not fetch it until the code first uses it. So every call to r.getDriver().getName() inside the loop sends one more query. Nothing in that line looks like database access, which is why the problem survives code review.
Twenty extra queries on a laptop take a few milliseconds. The same code over two thousand rides, against a database on another machine, takes seconds. The fix is to load the drivers together with the rides in a single query, using a fetch join (join fetch in JPQL) or an entity graph, a declaration of which related entities (the objects mapped to tables) to load together.
So when an endpoint that returns a list gets slower as the list grows, count the queries it sends before looking anywhere else.
- Spring Boot
- Performance
← → move through sections and cards