पाठ 13 / 25

Lazy and Eager Loading

Choose fetch types, understand proxies and avoid LazyInitializationException.

Loading what you need, when you need it

Fetch type controls when related data is loaded. Lazy loading returns a proxy (for single-valued associations) or an uninitialised collection, and loads the data only when it is first accessed. Eager loading loads it immediately with the owner. The JPA defaults are a trap: @ManyToOne and @OneToOne are EAGER by default, while @OneToMany and @ManyToMany are LAZY. Eager associations cannot be made lazy per query, cause extra joins or queries everywhere and cascade into large object graphs, so the widely recommended practice is to make every association LAZY and fetch what each use case needs explicitly. Accessing a lazy association after the persistence context has closed (for example in a controller after the service transaction ended) throws LazyInitializationException. Fix it by fetching the data inside the transaction (join fetch, entity graphs, DTO projections), not by switching to eager loading or relying on Open Session in View, which keeps the persistence context open through view rendering, hiding the problem while issuing unplanned queries from the web layer.

Proxies loaded on demand

A lazy association starts as a placeholder and is loaded when first accessed inside the persistence context.

A solid entity box linked to a hollow dashed box; an arrow from the hollow box to a database cylinder fills it in.
Figure 5.1 — A lazy proxy initialised on first access.

Lazy everywhere, fetched explicitly

Override the eager default and load associations deliberately.

@Entity
public class OrderLine {
    @Id @GeneratedValue private Long id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)     // override EAGER default
    private PurchaseOrder order;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Product product;
}

@Service
public class OrderQueryService {
    @Transactional(readOnly = true)
    public OrderView load(Long id) {
        PurchaseOrder o = em.createQuery(
                "select o from PurchaseOrder o join fetch o.customer where o.id = :id",
                PurchaseOrder.class)
            .setParameter("id", id)
            .getSingleResult();
        return OrderView.from(o);              // map to a DTO inside the transaction
    }
}

// controller, outside the transaction:
// order.getLines().size()   -> LazyInitializationException if lines were not fetched

Disable open-in-view

Set spring.jpa.open-in-view=false. Lazy-loading errors then appear in development, pointing to exactly the use cases that need explicit fetching, instead of turning into hidden extra queries in production.

त्वरित जाँच: What is the default fetch type of @ManyToOne in JPA?

  • EAGER
  • LAZY
  • NONE
  • It depends on the database
Answer

EAGER — @ManyToOne and @OneToOne default to EAGER; best practice is to set them to LAZY explicitly.