Introduction
Relationships are the foundation of every enterprise application.
Consider these examples:
- Customer → Orders
- Order → Order Items
- Employee → Department
- User → Roles
- Student → Courses
In relational databases, these relationships are represented using foreign keys.
In JPA, they are represented using annotations.
Poor relationship design often causes:
❌ N+1 queries
❌ Performance issues
❌ Circular references
❌ Excessive SQL
❌ Large object graphs
❌ Memory problems
This article covers:
✅ One-to-One
✅ One-to-Many
✅ Many-to-One
✅ Many-to-Many
✅ Join tables
✅ Cascade operations
✅ Orphan removal
✅ Bidirectional relationships
✅ Fetch types
Sample Database Model
DEPARTMENT
|
| 1
|
N
EMPLOYEE
|
| 1
|
N
ADDRESS
USER
|
N
|
N
ROLE
Relationship Types
| Relationship | Example |
|---|---|
| One-to-One | Employee → Passport |
| One-to-Many | Department → Employees |
| Many-to-One | Employee → Department |
| Many-to-Many | User → Roles |
One-to-One Relationship
Example:
Employee
|
1
|
1
Passport
Database
EMPLOYEE
---------
EMP_ID
PASSPORT
---------
PASSPORT_ID
EMP_ID
Employee Entity
@Entity
public class Employee {
@Id
private Long id;
@OneToOne
@JoinColumn(name = "PASSPORT_ID")
private Passport passport;
}
Passport Entity
@Entity
public class Passport {
@Id
private Long id;
}
One-to-Many
Example:
Department
|
1
|
N
Employee
Database
EMPLOYEE
---------
EMP_ID
DEPT_ID
Department Entity
@OneToMany(
mappedBy = "department")
private List<Employee> employees;
Employee Entity
@ManyToOne
@JoinColumn(name = "DEPT_ID")
private Department department;
Many-to-One
This is the most common JPA relationship.
Examples:
- Employee → Department
- Order → Customer
- Invoice → Account
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "DEPT_ID")
private Department department;
Why ManyToOne Should Usually Be LAZY
Bad:
@ManyToOne(fetch = FetchType.EAGER)
Loading 100 employees:
Employee
Department
Manager
Location
Many extra queries.
Good:
@ManyToOne(fetch = FetchType.LAZY)
Load only when needed.
Many-to-Many
Example:
User
N
\
USER_ROLE
/
N
Role
Join Table
USER_ROLE
----------
USER_ID
ROLE_ID
User Entity
@ManyToMany
@JoinTable(
name = "USER_ROLE",
joinColumns =
@JoinColumn(name="USER_ID"),
inverseJoinColumns =
@JoinColumn(name="ROLE_ID")
)
private Set<Role> roles;
Why Many-to-Many Can Be Dangerous
Problems:
- Complex SQL
- Large joins
- Difficult updates
Recommendation:
Create an explicit entity.
UserRole
JoinColumn
@JoinColumn(
name = "DEPT_ID")
Defines:
- Foreign key column.
- Relationship owner.
mappedBy
Example:
@OneToMany(
mappedBy = "department")
Meaning:
The Employee entity owns the relationship.
Avoid duplicate foreign keys.
Unidirectional Relationship
Employee
|
Department
Only Employee knows Department.
Bidirectional Relationship
Employee ←→ Department
Both sides know each other.
Example
@OneToMany(
mappedBy = "department")
private List<Employee> employees;
@ManyToOne
private Department department;
Cascade Operations
| Type | Description |
|---|---|
| PERSIST | Save child |
| MERGE | Update child |
| REMOVE | Delete child |
| ALL | Everything |
Example
@OneToMany(
cascade = CascadeType.ALL)
private List<Address> addresses;
Saving Employee:
Employee
Address
Address
Everything persists.
Orphan Removal
@OneToMany(
orphanRemoval = true)
Removing child:
employee.getAddresses()
.remove(address);
Database:
Address deleted.
Fetch Types
| Type | Meaning |
|---|---|
| EAGER | Load immediately |
| LAZY | Load later |
Default Fetch Types
| Relationship | Default |
|---|---|
| OneToMany | LAZY |
| ManyToOne | EAGER |
| OneToOne | EAGER |
| ManyToMany | LAZY |
Recommended Strategy
| Relationship | Recommendation |
|---|---|
| ManyToOne | LAZY |
| OneToMany | LAZY |
| ManyToMany | LAZY |
| OneToOne | LAZY |
EAGER Problem
Employee employee =
repository.findById(1L);
Generated:
SELECT employee
SELECT department
SELECT manager
SELECT location
Many unnecessary queries.
LAZY Loading
employee.getDepartment();
Only then:
SELECT *
FROM DEPARTMENT
N+1 Query Problem
Example:
employees.forEach(
e -> e.getDepartment());
Queries:
1 Employee query
100 Department queries
Total:
101 queries
Join Fetch Solution
@Query("""
select e
from Employee e
join fetch e.department
""")
One query.
SQL Generated
SELECT *
FROM EMPLOYEE E
JOIN DEPARTMENT D
ON E.DEPT_ID=D.DEPT_ID
Entity Graph
@EntityGraph(
attributePaths = "department")
Loads associations efficiently.
Cascade Best Practices
| Relationship | Recommendation |
|---|---|
| Parent → Child | Cascade |
| ManyToMany | Avoid ALL |
| Reference Data | No cascade |
Equals and HashCode
Avoid:
equals(id)
before persistence.
Recommended:
Business key
or
Generated identifier after persist
JSON Serialization Problem
Bidirectional:
Department
Employees
Department
Employees
Infinite loop.
Jackson Solution
@JsonManagedReference
@JsonBackReference
or:
@JsonIgnore
Recommended Entity
@Entity
public class Employee {
@Id
private Long id;
@ManyToOne(
fetch = FetchType.LAZY)
private Department department;
}
Department
@Entity
public class Department {
@OneToMany(
mappedBy = "department")
private List<Employee> employees;
}
Common Mistakes
EAGER Everywhere
Bad.
Cascade ALL on ManyToMany
Dangerous.
Bidirectional Everything
Unnecessary.
Large Object Graphs
Memory issues.
Missing Join Fetch
N+1 queries.
Best Practices
✅ Prefer LAZY.
✅ Use join fetch.
✅ Avoid ManyToMany.
✅ Use explicit join entities.
✅ Keep relationships small.
✅ Use DTOs.
✅ Avoid exposing entities to REST.
✅ Monitor SQL.
Interview Questions
Difference between OneToMany and ManyToOne?
OneToMany is parent side.
ManyToOne owns foreign key.
What is mappedBy?
Indicates relationship owner.
What causes N+1?
Lazy loading in loops.
Why avoid EAGER?
Excessive queries.
What is orphanRemoval?
Deletes removed children.
Why avoid ManyToMany?
Complex SQL and updates.
Summary
This article covered:
✅ OneToOne
✅ OneToMany
✅ ManyToOne
✅ ManyToMany
✅ Join tables
✅ Fetch types
✅ Cascades
✅ Orphan removal
✅ N+1 problems
✅ Join fetch
Next Article
Part 7 – Composite Keys and Embedded IDs
Topics:
- Composite keys
- EmbeddedId
- IdClass
- Natural keys
- Surrogate keys
- Junction entities
- equals and hashCode
- UUIDs
- Shared primary keys
- Best practices