Fetch and Cascade Types in JPA
Fetch and Cascade types control how related entities are loaded and how operations propagate from one entity to another.
Misusing these two concepts is one of the top reasons for:
- N+1 query problem
- Slow APIs
- Accidental deletes
- Data corruption
Understanding them clearly is mandatory for real-world systems.
Fetch Type vs Cascade Type
Fetch Type
π HOW data is loaded
- When related entities are fetched
- Impacts performance and SQL execution
Cascade Type
π HOW operations are propagated
- What happens to child entities when parent is saved/deleted
Golden Rule:
Fetch = loading behavior
Cascade = operation behavior
They are independent concepts.
Fetch Types in JPA
JPA supports two fetch types:
FetchType.EAGERFetchType.LAZY
FetchType.EAGER
What It Means
- Related entities are loaded immediately
- Happens in the same query or additional queries
Example
@ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
Behavior:
- Whenever Order is fetched β Customer is fetched
- Even if Customer is not needed
Problems:
- β Unnecessary data loading
- β Hidden extra SQL queries
- β Performance issues
Default for ManyToOne and OneToOne is EAGER.
FetchType.LAZY (RECOMMENDED)
What It Means
- Related entity is loaded only when accessed
- Uses proxy objects
Example
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
Behavior
- Customer is NOT loaded initially
- Loaded only when getCustomer() is called
Benefits
- β Better performance
- β Less memory usage
- β Predictable SQL
Best Practice: Always prefer LAZY unless you have a strong reason.
LazyInitializationException (Very Common)
Why It Happens
Customer customer = service.getCustomer(id);
customer.getOrders(); // β Exception
Reason:
- Transaction already ended
- Persistence context is closed
- Lazy collection cannot be loaded
Solutions (High-Level)
- Fetch data inside transaction
- Use DTO projections
- Use fetch joins (JPQL)
- Avoid exposing entities to controllers
Always override defaults explicitly.
Cascade Types in JPA
Cascade types define which operations propagate from parent to child.
Common Cascade Types
PERSISTMERGEREMOVEREFRESHDETACHALL
CascadeType.PERSIST
Saving parent also saves child
Example
@OneToMany(cascade = CascadeType.PERSIST)
private List<Order> orders;
entityManager.persist(customer);
- β Orders are saved automatically
CascadeType.MERGE
Merging parent also merges child
Used when:
- Updating detached entities
CascadeType.REMOVE
Deleting parent deletes child
@OneToMany(cascade = CascadeType.REMOVE)
- β Dangerous in banking systems
Example risk:
- Deleting customer β deletes all orders β
- Never use REMOVE blindly.
CascadeType.ALL
Applies all cascade operations
- β Very risky
- β Often misused
Avoid CascadeType.ALL unless entity lifecycle is tightly bound.
When Cascade Makes Sense
- β Parent truly owns child
- β Child cannot exist without parent
Example:
- Order β OrderItems
- Invoice β InvoiceLines
When Cascade Is Dangerous
- β Customer β Orders
- β Account β Transactions
Banking / Enterprise Best Practices
In banking systems:
- Use LAZY fetch almost everywhere
- Avoid cascading deletes
- Prefer explicit saves and deletes
- Treat parent and child lifecycles independently
Common Mistakes
- β Using default fetch types
- β Using EAGER everywhere
- β Using CascadeType.ALL blindly
- β Relying on lazy loading in controllers
- β Deleting parent without understanding cascade
summary
- Fetch controls data loading
- Cascade controls operation propagation
- LAZY is preferred over EAGER
- Cascade REMOVE is dangerous
- Defaults are unsafe, override explicitly
- Fetch and cascade are independent
Key Takeaway:
- Fetch types impact performance.
- Cascade types impact data safety.
- Misusing either can break production systems.