0Pricing
Java Academy · Lesson

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

  1. Entity Mapping with JPA Annotations
  2. Spring Data Repositories and Query Methods
  3. One-to-Many and Many-to-Many Relationships
  4. Pagination, Sorting, and Projections
← Back to Java Academy