LAZY vs EAGER

Two Loading Strategies

  • EAGER: the association is loaded together with the entity, always, whether you use it or not.
  • LAZY: Hibernate puts a placeholder (a proxy for a single entity, a persistent collection for a list) and runs the SELECT only when you first access it.
AnnotationDefault fetch typeRecommended
@ManyToOneEAGERLAZY
@OneToOneEAGERLAZY
@OneToManyLAZYLAZY
@ManyToManyLAZYLAZY
@ElementCollectionLAZYLAZY

Tip: Rule of thumb used by the Hibernate team: make every association LAZY, and decide per query what to load. EAGER is decided once in the mapping and cannot be switched off for one query.

Seeing LAZY Loading in the Log

@Transactional(readOnly = true)
public void show(Long id) {
    Product p = productRepository.findById(id).orElseThrow();   // SELECT products only
    System.out.println(p.getName());
    Category c = p.getCategory();                                // proxy, no SQL yet
    System.out.println(c.getId());                               // id is known: still no SQL
    System.out.println(c.getName());                             // first real access: SELECT categories
}

Console output

productService.show(7L);

Example output

DEBUG org.hibernate.SQL : select p1_0.id,p1_0.category_id,p1_0.description,p1_0.name,p1_0.price,p1_0.stock from products p1_0 where p1_0.id=?
Boat Airdopes 141
3
DEBUG org.hibernate.SQL : select c1_0.id,c1_0.name from categories c1_0 where c1_0.id=?
Electronics

What EAGER Costs

With @ManyToOne left EAGER, findById joins the category in one SQL. That looks good, but every query that loads products, even one that only needs names, now also loads categories. In JPQL queries, EAGER associations are not joined but loaded with extra SELECTs afterwards, which is the N+1 problem in disguise.

-- findById with EAGER category: a join
select p1_0.id,p1_0.category_id,c1_0.id,c1_0.name,p1_0.description,p1_0.name,p1_0.price,p1_0.stock
from products p1_0 join categories c1_0 on c1_0.id=p1_0.category_id where p1_0.id=?

-- "select p from Product p" with EAGER category: one extra select per distinct category
select p1_0.id,p1_0.category_id,p1_0.description,p1_0.name,p1_0.price,p1_0.stock from products p1_0
select c1_0.id,c1_0.name from categories c1_0 where c1_0.id=?
select c1_0.id,c1_0.name from categories c1_0 where c1_0.id=?
select c1_0.id,c1_0.name from categories c1_0 where c1_0.id=?