Question 23 · Section 16

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.

Language versions: English Russian Ukrainian

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

  1. mappedBy - indicates that the relationship is managed on the other side
  2. @JoinColumn - specifies the foreign key column
  3. 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]]