How to Properly Use @OneToMany and @ManyToOne
@OneToMany and @ManyToOne are the most common annotations for relationships in JPA. Correct usage is critical for performance and database correctness.
Overview
@OneToMany and @ManyToOne are the most common annotations for relationships in JPA. Correct usage is critical for performance and database correctness.
Junior Level
What is @ManyToOne and @OneToMany
@ManyToOne - many orders to one user.
@OneToMany - one user to many orders.
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
}
Important Rules
mappedBy- indicates that the relationship is managed on the other side@JoinColumn- specifies the foreign key column- LAZY - use for both sides
Example
// Order - owner side (contains FK)
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
// User - inverse side (doesn't contain FK)
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
}
Middle Level
Bidirectional Relationship
// Owner side - Order (contains FK column)
@Entity
public class Order {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
// Inverse side - User
@Entity
public class User {
@Id
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// Helper method - mandatory!
public void addOrder(Order order) {
orders.add(order);
order.setUser(this); // important!
}
public void removeOrder(Order order) {
orders.remove(order);
order.setUser(null); // important!
}
}
Why Helper Methods Are Mandatory
// Without helper method
User user = new User();
Order order = new Order();
user.getOrders().add(order);
// order.getUser() = null! -> FK not set
// With helper method
User user = new User();
Order order = new Order();
user.addOrder(order); // sets both sides
// order.getUser() == user OK
Common Mistakes
// mappedBy on inverse side (User - inverse, Order - owner):
@OneToMany(mappedBy = "user") // correct: User - inverse side
private List<Order> orders;
// mappedBy on owner side (Order) - USELESS:
// @ManyToOne(mappedBy = ...) - such attribute doesn't exist on @ManyToOne
// Without collection initialization
private List<Order> orders; // NullPointerException on add
private List<Order> orders = new ArrayList<>(); // correct
// EAGER for collection
@OneToMany(fetch = FetchType.EAGER) // N+1 problem
private List<Order> orders;
// What happens in SQL with EAGER on collection:
// SELECT * FROM users; -- 1 query
// SELECT * FROM orders WHERE user_id = 1; -- N queries (one per user)
// -- Total: 1 + N queries instead of 1 JOIN
Senior Level
Internal Implementation
@ManyToOne - FK column in Order table
@OneToMany(mappedBy) - no FK column, this is inverse side
Owner side (@ManyToOne):
- Manages FK column
- persist(order) -> INSERT with user_id
- merge(order) -> UPDATE user_id
Inverse side (@OneToMany(mappedBy)):
- Doesn't manage FK
- mappedBy = field name on owner side
- Only for reverse navigation
Unidirectional vs Bidirectional
// Unidirectional @ManyToOne (only Order -> User)
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
// Unidirectional @OneToMany (only User -> Orders)
@Entity
public class User {
@OneToMany
@JoinColumn(name = "user_id") // FK on Order side
private List<Order> orders;
}
// Bidirectional - both directions
// Order knows about User, User knows about Orders
// Requires synchronization of both sides
When to Use What
Unidirectional @ManyToOne:
- Simpler, no synchronization needed
- When User -> Orders navigation not needed
- Less code
Bidirectional:
- When navigation in both directions needed
- When cascade with orphanRemoval needed
- Requires helper methods
Optimization
// @JoinColumn on unidirectional @OneToMany (Hibernate 6+)
@Entity
public class User {
@OneToMany
@JoinColumn(name = "user_id",
foreignKey = @ForeignKey(name = "fk_user_orders"),
referencedColumnName = "id")
private List<Order> orders;
}
// Hibernate 6+: no join table created
Best Practices
LAZY for both sides
Helper methods for synchronization
mappedBy on inverse side
Initialize collections
@JoinColumn on owner side
orphanRemoval for composite children
EAGER for collections
Without helper methods
mappedBy on owner side
Without collection initialization
Unidirectional @OneToMany with join table
Interview Cheat Sheet
Must know:
- @ManyToOne - owner side (contains FK), @OneToMany(mappedBy) - inverse side (no FK)
- LAZY for both sides - golden rule
- Helper methods mandatory for bidirectional: addOrder/removeOrder synchronize both sides
- mappedBy points to owner side field - without it Hibernate creates a join table
- Always initialize collections: new ArrayList<>() - otherwise NullPointerException
- Unidirectional @OneToMany creates a join table (before Hibernate 6), in Hibernate 6+ can use @JoinColumn
Frequent follow-up questions:
- Why are helper methods mandatory? Adding only to collection without setUser() -> FK = null, desynchronization
- What if without mappedBy? Hibernate creates an unnecessary join table - additional overhead
- Why is EAGER for collection bad? N+1 problem: loading all children for each parent
- When to use unidirectional @OneToMany? When User -> Orders navigation not needed, simpler
Red flags (DO NOT say):
- “EAGER for @OneToMany” - N+1 problem guaranteed
- “Without helper methods for bidirectional” - desynchronization, FK not set
- “mappedBy on owner side” - such attribute doesn’t exist on @ManyToOne
- “Collection without initialization” - NullPointerException on add
Related topics:
- [[24. What Are the Peculiarities of Bidirectional Relationships]]
- [[22. What is Orphan Removal]]
- [[2. What is the Difference Between Lazy and Eager Loading]]
- [[1. What is the N+1 Problem and How to Solve It]]