The N+1 query problem is the single most common performance trap in Spring Data JPA code, and it hides in the most innocent-looking loops. You fetch a list of entities, iterate over them, touch a lazy association, and Hibernate quietly fires one extra query per row.
Spotting it
The first step is always visibility. I turn on SQL logging in a test profile so the count is impossible to ignore. If a single endpoint fans out into forty queries, you will see forty lines.
spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.SQL=DEBUGThe three fixes I reach for
A JOIN FETCH in a JPQL query solves the common case cleanly. An entity graph keeps the fetch plan out of the query string when the same repository method is reused. And for read-heavy list endpoints, a projection into a DTO sidesteps the association entirely.
Fix the query plan, not the symptom. A cache in front of an N+1 just hides a design problem behind latency you will pay for later.
Whichever tool you choose, verify it with the same SQL log you used to find the problem. The query count is the metric that matters.