Fetching and N+1
What is the difference between LAZY and EAGER fetching?
EAGER loads the association together with the entity (a join or an extra SELECT) every time, even when you do not need it. LAZY puts a proxy (for single references) or a persistent collection wrapper in place and runs the SELECT only on first access. LAZY is the right default; EAGER cannot be turned off per query, while LAZY can be fetched per query.
What is the N+1 select problem?
One query loads N parents, then accessing a lazy association on each parent fires one more query per parent: 1 + N queries instead of 1. It is the most common JPA performance bug.
-- orderRepository.findAll() then order.getUser().getName() in a loop
select * from orders; -- 1 query, returns 50 orders
select * from users where id = 7; -- +1
select * from users where id = 9; -- +1 ... 50 moreNote: EAGER does not fix it: findAll() with an EAGER @ManyToOne still runs a secondary SELECT per distinct user.
How do you fix the N+1 problem?
- JOIN FETCH in JPQL:
select o from Order o join fetch o.user. - @EntityGraph on a repository method:
@EntityGraph(attributePaths = {"user", "items"}). - Batch fetching:
@BatchSize(size = 50)orhibernate.default_batch_fetch_size=50loads lazy associations withwhere id in (...). - DTO projection: select only the columns the screen needs.
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("select o from Order o join fetch o.user where o.status = :status")
List<Order> findWithUser(@Param("status") OrderStatus status);
@EntityGraph(attributePaths = "items")
List<Order> findByUserId(Long userId);
}What causes LazyInitializationException and how do you fix it?
Accessing an uninitialized lazy proxy or collection after the persistence context (Session) is closed, e.g. in a controller or JSON serializer after the @Transactional service returned.
- Fetch what you need inside the transaction (JOIN FETCH / @EntityGraph).
- Map to a DTO inside the service and return the DTO, not the entity.
- Do not "fix" it with EAGER,
open-in-vieworhibernate.enable_lazy_load_no_trans: they hide N+1 problems.
What is Open Session in View (spring.jpa.open-in-view)? Should you disable it?
Spring Boot enables it by default (and logs a warning). It keeps the EntityManager open for the whole HTTP request, so lazy loading works in controllers and views. Downsides: a DB connection may be held for the full request, and lazy loads happen outside transactions as hidden N+1 queries. Most teams set spring.jpa.open-in-view=false and fetch explicitly in the service layer.
Why do JOIN FETCH with pagination and fetching two lists together cause problems?
- Collection fetch + pagination: SQL LIMIT would cut parent rows in half, so Hibernate loads all rows and paginates in memory (warning HHH90003004). Fix: page the parent ids first, then fetch the collections for those ids; or use batch fetching.
hibernate.query.fail_on_pagination_over_collection_fetch=trueturns the warning into an error. - Two bags (
Listcollections) join-fetched at once throwMultipleBagFetchExceptionand would create a Cartesian product. Fetch one collection per query, or useSet.