let’s put it all together with two clean, full examples side by side: one where batch fetching works (correct) and one where it doesn’t work (incorrect).
I’ll give you them both without extra comments, so you can clearly compare.
✅ Working example — batch fetching will work (one transaction/session)
@Service
@RequiredArgsConstructor
public class AuthorService {
private final EntityManager em;
@Transactional // single transaction/session for entire method
public void loadAuthorsAndBooksInSingleSession() {
List<Author> authors = em.createQuery(
"FROM Author", Author.class
).setMaxResults(20).getResultList();
for (Author author : authors) {
List<Book> books = author.getBooks();
System.out.println("Author: " + author.getName() + ", books: " + books.size());
}
}
}
🚫 Failing example — batch fetching will NOT work (multiple transactions/sessions)
@Service
@RequiredArgsConstructor
public class AuthorService {
private final EntityManager em;
public void loadAuthorsAndBooksInMultipleSessions() {
List<Author> authors = em.createQuery(
"FROM Author", Author.class
).setMaxResults(20).getResultList();
for (Author author : authors) {
loadBooksInSeparateTransaction(author);
}
}
@Transactional // each call creates a new transaction/session
public void loadBooksInSeparateTransaction(Author author) {
List<Book> books = author.getBooks();
System.out.println("Author: " + author.getName() + ", books: " + books.size());
}
}
✅ Key difference
- First example:
@Transactional
wraps loading authors and accessing their lazy collections in a single session → Hibernate sees all lazy proxies together → batch fetching works. - Second example: each call to
loadBooksInSeparateTransaction()
opens a new session → proxies are isolated → batch fetching can’t happen → you’ll get separate queries for each author’s collection.
✅ Working example (single session, batching works)
Given @BatchSize(size = 10)
on Author.books
and loading 20 authors:
SQL executed:
-- 1) Load authors (all 20 in one query)
select * from authors limit 20;
-- 2) Load books for first batch of 10 authors in a single query
select * from books where author_id in (1,2,3,4,5,6,7,8,9,10);
-- 3) Load books for second batch of 10 authors in a single query
select * from books where author_id in (11,12,13,14,15,16,17,18,19,20);
✔ Total queries: 3
✔ Hibernate groups proxies for 10 authors’ books per batch → batch fetching works.
🚫 Failing example (multiple sessions, batching fails)
Again with 20 authors and @BatchSize(size = 10)
, but now each author’s books are loaded in a separate transaction/session:
SQL executed:
-- 1) Load authors (all 20 in one query)
select * from authors limit 20;
-- 2) Load books for each author in separate queries:
select * from books where author_id = 1;
select * from books where author_id = 2;
select * from books where author_id = 3;
...
select * from books where author_id = 20;
✔ Total queries: 21
✔ Hibernate sees proxies for only one author at a time → no batching happens → classic N+1 problem.
🔥 Key difference:
- When authors and lazy collections live in the same session → Hibernate groups lazy proxies → batch fetching combines them into fewer queries.
- When proxies are spread across multiple sessions → each proxy loads separately → no batching → N+1.