✅ 1) Join fetches (JOIN FETCH
in JPQL/HQL)
- Load parent and associated entities in one SQL query.
- Example:
SELECT DISTINCT a FROM Author a JOIN FETCH a.books
Pros: very effective; simple.
Cons: can cause Cartesian product explosions when fetching multiple collections.
✅ 2) Entity graphs (EntityGraph
API)
- Declaratively specify what associations to eagerly fetch without hardcoding joins in queries.
- Example:
EntityGraph<Author> graph = em.createEntityGraph(Author.class);
graph.addAttributeNodes("books");
em.createQuery("FROM Author", Author.class)
.setHint("javax.persistence.fetchgraph", graph)
.getResultList();
Pros: flexible; keeps queries clean.
Cons: JPA 2.1+ only.
✅ 3) Batch fetching (@BatchSize
or hibernate.default_batch_fetch_size
)
- Loads multiple lazy proxies in batches, reducing N queries to N/batchSize queries.
- Example:
@OneToMany(fetch = FetchType.LAZY)
@BatchSize(size = 10)
private List<Book> books;
Pros: keeps lazy loading; effective for many-to-one or one-to-many.
Cons: still issues multiple queries.
✅ 4) Subselect fetching (@Fetch(FetchMode.SUBSELECT)
)
- Loads collections for a list of parent entities in a single subselect query:
@OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<Book> books;
Pros: single secondary query; avoids Cartesian explosion.
Cons: large parent lists can produce large IN (...)
clauses.
✅ 5) Adjust fetch type to EAGER
(with caution)
- Eager associations load automatically with the parent entity.
- Example:
@OneToMany(fetch = FetchType.EAGER)
private List<Book> books;
Pros: eliminates lazy loading delays.
Cons: dangerous — can cause unexpected massive joins or performance hits if used blindly.
✅ 6) Manual queries with WHERE IN (...)
- Write explicit queries to load all needed associations in one go.
- Example:
SELECT b FROM Book b WHERE b.author.id IN :authorIds
Pros: complete control.
Cons: manual; increases boilerplate.
✅ 7) Hibernate second-level cache
- Does not solve N+1 itself but mitigates its cost by avoiding repeated DB hits for the same entities.
- Example:
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
Pros: useful for read-heavy apps.
Cons: adds complexity; not a direct solution.
🔥 Key takeaway:
The best way to avoid N+1 in most cases is to either use
JOIN FETCH
,EntityGraph
, or batch/subselect fetching — depending on the data shape, query, and your tolerance for Cartesian products or largeIN
clauses.