✅ Short Answer
- A normal join (
JOIN
,LEFT JOIN
) combines tables/entities for filtering or constraints, but does not fetch related entities into your object graph. - A fetch join (
JOIN FETCH
) loads the associated entities eagerly along with the parent in a single query, populating their fields immediately — crucial to avoid the N+1 problem.
🔎 Detailed Explanation
🔹 Normal Joins
- Used when you want to filter or restrict results based on related entity data.
- Does not load associated entities into memory → they remain lazily loaded if marked as lazy.
- Typically returns only the parent entity or fields selected.
Example:
String hql = "SELECT o FROM Order o JOIN o.customer c WHERE c.status = :status";
List<Order> orders = em.createQuery(hql, Order.class)
.setParameter("status", "ACTIVE")
.getResultList();
✅ This filters orders by customer status.
❌ But o.customer
remains uninitialized (lazy) → accessing order.getCustomer()
later can still trigger an extra SQL query.
🔹 Fetch Joins
- Uses
JOIN FETCH
to tell Hibernate/JPA to load the related entities right away. - Fetched entities are fully initialized and attached to the parent.
- Avoids lazy-loading and solves the N+1 problem.
Example:
String hql = "SELECT o FROM Order o JOIN FETCH o.customer c WHERE c.status = :status";
List<Order> orders = em.createQuery(hql, Order.class)
.setParameter("status", "ACTIVE")
.getResultList();
✅ Here, each Order
comes with its Customer
already loaded, even if customer
was mapped as lazy.
📊 Quick Comparison Table
Feature | Normal JOIN | JOIN FETCH |
---|---|---|
Purpose | Filtering/constraints | Fetching associations eagerly |
Loads child entity? | ❌ No | ✅ Yes |
Solves N+1? | ❌ No | ✅ Yes |
Returns | Parent or selected fields | Parent with initialized associations |
🔹 Important Notes
✅ Both joins use entity relationships → no SQL ON clauses.
❗ Fetch joins cannot be used in subqueries → only top-level JPQL/HQL.
❗ Fetch joins on collections can cause duplicate parent rows → careful with pagination.