Fetching strategy in JPA determines when related entities are loaded from the database. Choosing between Lazy and Eager fetching has a direct impact on application performance, memory usage, and query efficiency.
- Lazy Fetching: Related entities are loaded only when accessed.
- Eager Fetching: Related entities are loaded immediately with the parent entity.
Think of it like ordering food:
- Lazy = You order when you’re hungry (on-demand).
- Eager = You order everything upfront, whether you need it or not.
In this tutorial, we’ll cover Lazy vs Eager fetching in JPA, with code examples, SQL outputs, pitfalls, and best practices.
Setup and Configuration
persistence.xml
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
<persistence-unit name="my-pu" transaction-type="RESOURCE_LOCAL">
<class>com.example.Department</class>
<class>com.example.Employee</class>
<properties>
<property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:testdb"/>
<property name="jakarta.persistence.jdbc.user" value="sa"/>
<property name="jakarta.persistence.jdbc.password" value=""/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
Entity Example
import jakarta.persistence.*;
import java.util.List;
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Employee> employees;
// Getters and setters
}
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "department_id")
private Department department;
// Getters and setters
}
Lazy Fetching
Example
Department dept = em.find(Department.class, 1L);
System.out.println(dept.getName()); // Employees not loaded yet
// Employees loaded only when accessed
for (Employee e : dept.getEmployees()) {
System.out.println(e.getName());
}
SQL Output:
select d.id, d.name from departments d where d.id=1;
select e.id, e.name, e.department_id from employees e where e.department_id=1;
Eager Fetching
Example
Employee emp = em.find(Employee.class, 1L);
System.out.println(emp.getName()); // Department is loaded immediately
System.out.println(emp.getDepartment().getName());
SQL Output:
select e.id, e.name, e.department_id, d.id, d.name
from employees e
left join departments d on e.department_id=d.id
where e.id=1;
CRUD with Fetching Strategies
Create
tx.begin();
Department dept = new Department();
dept.setName("IT");
Employee e1 = new Employee();
e1.setName("Alice");
e1.setDepartment(dept);
dept.setEmployees(List.of(e1));
em.persist(dept);
tx.commit();
Read (Lazy vs Eager)
- Lazy: Employees loaded only when accessed.
- Eager: Department loaded immediately with Employee.
Update
tx.begin();
e1.setName("Alice Updated");
em.merge(e1);
tx.commit();
Delete
tx.begin();
em.remove(dept);
tx.commit();
Persistence Context and Fetching
- Persistence Context ensures one entity instance per transaction.
- Lazy fetching may throw
LazyInitializationException
if accessed outside the persistence context. - Eager fetching avoids this but may load unnecessary data.
Querying with JPA
JPQL
List<Department> depts = em.createQuery("SELECT d FROM Department d JOIN FETCH d.employees", Department.class)
.getResultList();
Criteria API
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Department> cq = cb.createQuery(Department.class);
Root<Department> root = cq.from(Department.class);
root.fetch("employees", JoinType.LEFT);
List<Department> results = em.createQuery(cq).getResultList();
Native SQL
List<Object[]> results = em.createNativeQuery("SELECT d.name, e.name FROM departments d JOIN employees e ON d.id=e.department_id").getResultList();
Performance Considerations
- Lazy fetching improves performance when related entities are rarely needed.
- Eager fetching can cause cartesian product explosions with multiple joins.
- Use JOIN FETCH in JPQL for controlled eager loading.
Real-World Use Cases
- Lazy: Large datasets (Orders → Items) where items are accessed rarely.
- Eager: Small, always-needed relationships (User → Profile).
Anti-Patterns and Pitfalls
- LazyInitializationException: Accessing lazy collections outside persistence context.
- Overusing Eager: Leads to massive joins and poor performance.
- Unidirectional One-to-Many with Eager: Very costly in large data models.
Best Practices
- Default to Lazy fetching.
- Use Eager only for small, always-required associations.
- Use
JOIN FETCH
in queries to optimize data loading. - Monitor generated SQL for tuning.
📌 JPA Version Notes
- JPA 2.0: Introduced Criteria API, Metamodel.
- JPA 2.1: Added Entity graphs for flexible fetching.
- Jakarta Persistence: Package renamed from
javax.persistence
→jakarta.persistence
.
Conclusion and Key Takeaways
- Lazy fetching = load on demand.
- Eager fetching = load immediately.
- Choose strategy based on data access patterns.
- Avoid pitfalls like
LazyInitializationException
and cartesian joins.
By mastering fetching strategies, you ensure optimized, production-ready JPA applications.
FAQ
1. What’s the difference between JPA and Hibernate?
JPA is a specification; Hibernate is an implementation.
2. How does JPA handle the persistence context?
It caches entities and tracks changes within a transaction.
3. What are the drawbacks of eager fetching in JPA?
It loads unnecessary data, impacting memory and performance.
4. How can I solve the N+1 select problem with JPA?
Use JOIN FETCH
, batch fetching, or entity graphs.
5. Can I use JPA without Hibernate?
Yes, EclipseLink and OpenJPA are alternatives.
6. What’s the best strategy for inheritance mapping in JPA?JOINED
for normalization, SINGLE_TABLE
for performance.
7. How does JPA handle composite keys?
With @EmbeddedId
or @IdClass
.
8. What changes with Jakarta Persistence?
Namespace migrated to jakarta.persistence
.
9. Is JPA suitable for microservices?
Yes, but DTOs and projections help reduce payloads.
10. When should I avoid using JPA?
In high-performance batch or NoSQL-driven apps.