Hibernate + Spring Data JPA: Lazy Loading Across Transaction Boundaries
0 reputation · 29 Apr 2021, 17:37 UTC
0 reputation · 29 Apr 2021, 17:37 UTC
Goal: Determine whether a Spring Data JPA repository method that returns an entity with a lazily loaded association should allow safe access to that association after the transaction ends without throwing an exception.
Current behavior: Hibernate throws a LazyInitializationException when the association is accessed outside of an active transaction, unless fetch joins or @Transactional are used. Some developers argue that returning an empty collection (or Optional.empty) would be safer for API consumers, but Hibernate has not settled on a default behavior, leaving the decision as an open design question.
Should Hibernate provide a configuration option to return empty collections (or null) for detached lazily loaded associations instead of throwing LazyInitializationException? What are the performance and correctness implications of enabling hibernate.enable_lazy_load_no_trans for this purpose? How should Spring Data JPA guide users to handle this scenario while maintaining portability across JPA providers?
35775 reputation · 30 Apr 2021, 03:19 UTC
No—Hibernate should not return empty collections (or null) for detached lazy associations, and hibernate.enable_lazy_load_no_trans is not a safe general-purpose fix. The portable solution in Spring Data JPA is to fetch everything you need inside the transaction using fetch joins, @EntityGraph, or DTO projections.
Confirmed: LazyInitializationException is intentional, JPA-consistent behavior. A lazy proxy is a placeholder that only knows how to load data while a persistence context (session) is attached. Once the transaction ends, the proxy cannot know whether the association has zero rows or was simply never loaded.
Returning an empty collection would conflate two very different states:
Likely explanation for why Hibernate won't adopt it: silently returning empty results would hide N+1 problems and missing-fetch bugs behind plausible-looking data, and business logic built on that data could make wrong decisions (e.g., "customer has no orders" when orders were never loaded). An explicit failure is far easier to diagnose than silent data corruption.
This Hibernate-specific flag (not standard JPA) opens a temporary session for each lazy access outside a transaction. It eliminates the exception, but at real costs:
Spring's OpenEntityManagerInViewFilter (the "Open Session in View" pattern, enabled by default in many Spring Boot versions) has the same trade-off: it keeps the session open through view rendering, trading the exception for hidden queries during serialization and longer connection hold times.
Decide what the caller needs and fetch it inside the transaction:
// Option 1: fetch join in JPQL
@Query("select o from Order o left join fetch o.lines where o.id = :id")
Optional<Order> findWithLines(@Param("id") Long id);
// Option 2: entity graph on a derived query
@EntityGraph(attributePaths = "lines")
Optional<Order> findById(Long id);
// Option 3: DTO projection — fetch only what the API returns
@Query("select new com.example.OrderDto(o.id, o.status) from Order o where o.id = :id")
Optional<OrderDto> findSummary(@Param("id") Long id);All three populate the data with a bounded number of queries, so access after the transaction ends is safe with any JPA provider.
LazyInitializationException with default settings.hibernate.enable_lazy_load_no_trans plus Hibernate statistics (or datasource-proxy), serialize the entity graph, and count the extra per-association queries.@EntityGraph or fetch join and confirm one query and no exception after the transaction ends.Note: exact defaults (including whether Open Session in View is on) vary across Hibernate 5/6 and Spring Boot versions—check your versions before relying on any behavior described here.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.