What is the difference between Lazy and Eager fetching?
In JPA (Java Persistence API), fetching strategies determine when associated entities are loaded from the database. This is a crucial aspect for optimizing performance and managing memory consumption in persistence layers.
Understanding Fetching Strategies
When you load an entity that has relationships with other entities (e.g., OneToOne, OneToMany, ManyToOne, ManyToMany), JPA needs to decide whether to load the associated entities immediately along with the primary entity, or to defer their loading until they are explicitly accessed. This decision is controlled by fetch types: EAGER and LAZY.
Eager Fetching
Eager fetching means that when the primary entity is loaded, all its associated entities marked as EAGER are fetched immediately from the database. This typically results in a single, more complex SQL query (e.g., using JOINs) that retrieves all necessary data upfront.
- When Loaded: At the time the primary entity is loaded.
- Performance: Can be less performant initially if many associations are loaded but not used. Reduces N+1 query problem if all associated data is needed.
- Memory: Potentially higher memory consumption as more objects are created upfront.
- Proxies: No proxies are used for eager fetched collections; the actual collection is returned.
- Default For: @ManyToOne, @OneToOne.
Example: Eager Fetching
@Entity
public class Order {
@Id private Long id;
private String orderNumber;
@ManyToOne(fetch = FetchType.EAGER) // Default for ManyToOne
private Customer customer;
// ... getters and setters
}
Lazy Fetching
Lazy fetching means that associated entities are not loaded from the database until they are explicitly accessed for the first time. When the primary entity is loaded, a proxy object or a placeholder is typically injected for the lazy-loaded association. The actual data is fetched only when a method on this proxy is called.
- When Loaded: Only when the associated data is accessed for the first time.
- Performance: Improves initial load time for the primary entity. Can lead to the N+1 query problem if many lazy associations are accessed in a loop without proper optimization (e.g.,
JOIN FETCH). - Memory: Lower initial memory consumption as objects are created on demand.
- Proxies: Requires a persistence context (transaction) to be open when accessing lazy-loaded data. Can cause
LazyInitializationExceptionif accessed outside a transaction. - Default For: @OneToMany, @ManyToMany.
Example: Lazy Fetching
@Entity
public class Customer {
@Id private Long id;
private String name;
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY) // Default for OneToMany
private List<Order> orders = new ArrayList<>();
// ... getters and setters
}
Key Differences
| Feature | Eager Fetching | Lazy Fetching |
|---|---|---|
| When Loaded | Immediately with the parent entity | On first access of the associated entity |
| SQL Queries | Fewer, but potentially larger, complex JOIN queries | Multiple, simpler queries (parent entity + separate queries for each accessed association) |
| Initial Performance | Can be slower due to loading more data | Faster, as only essential data is loaded |
| Memory Usage | Higher initial memory footprint | Lower initial memory footprint |
| Risk of N+1 | Lower, if all data is needed | Higher, if not properly managed (e.g., using JOIN FETCH) |
| LazyInitializationException | Not applicable | Possible if accessed outside a session/transaction |
| Default For | @ManyToOne, @OneToOne | @OneToMany, @ManyToMany |
Choosing the Right Strategy
The choice between EAGER and LAZY fetching largely depends on the specific use case and application access patterns. As a general best practice, it is often recommended to use lazy fetching for collections and, whenever possible, for single-valued associations, and then explicitly eager fetch specific data when needed using JOIN FETCH clauses in JPQL/Criteria queries. This approach provides more control and helps prevent performance bottlenecks and memory issues.