Watch JPA/Hibernate's quietest performance killer happen. Load a list of N rows, touch a lazy association, and it fires one more query per row — 1 + N. Then fix it with JOIN FETCH, @EntityGraph, or batch fetching and watch the query count collapse.
▶ Live demo · The fixes · Run locally
N+1 is the bug that doesn't look like a bug. With 5 rows in dev it's instant; in production with 500 rows it's 501 queries and a page that takes seconds. It never errors, so it hides until it hurts. Seeing the query log fill one line per row makes the problem — and the fix — obvious.
Pick N and a strategy, hit run, and watch every SQL statement appear with a live query count and total round-trip time:
- Lazy (default) →
SELECT * FROM orders(1) plusSELECT * FROM customer WHERE id=?once per order (N). That's the N+1: 1 + N queries for a single list.
- JOIN FETCH —
SELECT o, c FROM Order o JOIN FETCH o.customer→ 1 query, customers joined in. - @EntityGraph —
@EntityGraph(attributePaths = "customer")on the repository method → the same single JOIN, declaratively, no custom JPQL. - Batch fetching —
@BatchSize(5)(orhibernate.default_batch_fetch_size) keeps lazy loading but groups the child selects intoWHERE id IN (?,?,?,?,?)chunks → 1 + ⌈N/5⌉ queries. Great when a full join would multiply rows across several collections.
The "vs lazy" stat shows exactly how many round-trips each fix saves.
JOIN FETCH across two collections causes a cartesian product (rows multiply) — that's precisely when batch fetching wins. And FETCH + pagination (setMaxResults) makes Hibernate paginate in memory. There's no one right answer; this lets you feel the trade-off.
open index.html # macOS
start index.html # Windows
python -m http.server 8000 # or serve → http://localhost:8000A teaching model — the query counts and strategies match real Hibernate behaviour; timings are simulated. Turn on spring.jpa.show-sql=true (or hibernate.generate_statistics) in a real app to catch N+1 in your own logs.
Ideas: nested collections (cartesian product demo), @Fetch(SUBSELECT), DTO projections, pagination-with-fetch pitfall. PRs welcome.
If this saved you from an N+1 in prod, a ⭐ helps others find it.
MIT © dev48v