One-to-Many and Many-to-Many Relationships
Map @OneToMany, @ManyToMany, and @JoinTable with cascade types and fetch strategies.
One-to-Many and Many-to-Many Relationships is a free Java Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Relationship Types in JPA
JPA supports four relationship types: @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Each maps to foreign-key or join-table constraints.
@ManyToOne: The Owning Side
A many-to-one relationship (e.g., many Orders belong to one User) is mapped with @ManyToOne and a @JoinColumn for the foreign key.
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}@OneToMany: The Inverse Side
The inverse side (User has many Orders) uses @OneToMany(mappedBy = "user"). The mappedBy attribute points to the field on the owning side that holds the FK.
@Entity
public class User {
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
}Bidirectional Consistency Helper
With bidirectional associations, maintain both sides of the relationship in a helper method to keep the object graph consistent before flushing.
public void addOrder(Order order) {
orders.add(order);
order.setUser(this); // keep both sides in sync
}
public void removeOrder(Order order) {
orders.remove(order);
order.setUser(null);
}Cascade Types
CascadeType.ALL propagates all operations (persist, merge, remove, refresh, detach) from parent to child. PERSIST and MERGE are common safe choices. REMOVE deletes children when the parent is deleted.
@OneToMany(mappedBy = "user",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
orphanRemoval = true)
private List<Order> orders;orphanRemoval: Automatic Child Deletion
orphanRemoval = true deletes a child entity when it is removed from the parent's collection — even without explicit entityManager.remove().
user.getOrders().remove(order); // triggers DELETE for the orphaned order on flush@ManyToMany with @JoinTable
Many-to-many (e.g., Student and Course) uses a join table. Define it on the owning side with @JoinTable; the inverse side uses mappedBy.
@Entity
public class Student {
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private Set<Course> courses = new HashSet<>();
}Extra Columns on the Join Table
When the join table needs extra columns (e.g., enrollment date), convert @ManyToMany to two @OneToMany / @ManyToOne relationships with a new entity representing the join table.
@Entity
public class Enrollment {
@ManyToOne Student student;
@ManyToOne Course course;
private LocalDate enrolledAt;
}FetchType: LAZY vs EAGER
Default fetch: @ManyToOne and @OneToOne are EAGER (loads immediately). @OneToMany and @ManyToMany are LAZY (loads on access). Always use LAZY for collections to avoid N+1 queries.
// Always override to LAZY:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;N+1 Query Problem
Accessing a lazy collection inside a loop fires one query per entity (N+1). Fix with JOIN FETCH in JPQL or @EntityGraph to load the collection in one query.
// N+1 problem:
List<User> users = repo.findAll();
users.forEach(u -> u.getOrders().size()); // 1 + N queries!
// Fix:
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();Set vs List for Collections
Use Set for @ManyToMany (avoids duplicate join rows). Use List for @OneToMany when order matters. Never use List with @ManyToMany — it causes Hibernate to delete and re-insert all rows.
Quick Check
What does mappedBy indicate in a @OneToMany annotation?
Recap
Use @ManyToOne+@JoinColumn on the owning (FK) side. Use @OneToMany(mappedBy=...) on the inverse side. Always LAZY for collections. Fix N+1 with JOIN FETCH. Use a join entity for many-to-many with extra columns.
Frequently asked questions
Is the “One-to-Many and Many-to-Many Relationships” lesson free?
Yes — the full text of “One-to-Many and Many-to-Many Relationships” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “One-to-Many and Many-to-Many Relationships”?
Map @OneToMany, @ManyToMany, and @JoinTable with cascade types and fetch strategies. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “One-to-Many and Many-to-Many Relationships” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Entity Mapping with JPA Annotations
- Spring Data Repositories and Query Methods
- One-to-Many and Many-to-Many Relationships
- Pagination, Sorting, and Projections