LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 1519 min read

One-to-Many Mappings

Learn how to map one-to-many and many-to-one relationships in Hibernate using @OneToMany, @ManyToOne, and mappedBy, with a Department/Employee example.

Introduction

One-to-many is probably the most common relationship in real-world schemas: one Department has many Employees, one Order has many OrderItems, one Blog has many Comments. Hibernate models this with a pair of complementary annotations — @OneToMany on the "one" side and @ManyToOne on the "many" side — because a one-to-many relationship viewed from the other direction is simply a many-to-one.

What You Will Learn
  • How to model a bidirectional one-to-many / many-to-one relationship
  • Why mappedBy is required on the @OneToMany side
  • How to save a parent with several children in one operation
  • How Hibernate fetches collections of related entities

Modeling the Relationship

A Department contains a List of Employees; each Employee points back to exactly one Department. The foreign key, department_id, lives on the employees table — the "many" side always holds the foreign key in a one-to-many relationship.

Department.java
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
public Department() {}
public Department(String name) { this.name = name; }
public Long getId() { return id; }
public String getName() { return name; }
public List<Employee> getEmployees() { return employees; }
public void addEmployee(Employee employee) {
employees.add(employee);
employee.setDepartment(this);
}
}
Employee.java
@Entity
@Table(name = "employees")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;
public Employee() {}
public Employee(String name) { this.name = name; }
public Long getId() { return id; }
public String getName() { return name; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
}

Understanding mappedBy

Just as with @OneToOne, exactly one side owns the foreign key. In a one-to-many/many-to-one pair, the @ManyToOne side (Employee) always owns the relationship, because that is where the foreign key column physically lives. The @OneToMany side (Department) is always the inverse side, and must declare mappedBy pointing at the field name on Employee that refers back to it — here, "department".

SideEntityAnnotationOwns the FK?
Many sideEmployee@ManyToOne @JoinColumn(name = "department_id")Yes
One sideDepartment@OneToMany(mappedBy = "department")No — inverse side
A Helper Method Keeps Both Sides in Sync

The addEmployee() method on Department above updates the collection and sets the back-reference on Employee in one call, which avoids the classic bug of forgetting to link both sides.

Saving the Relationship

With cascade = CascadeType.ALL on the Department side, persisting a Department also persists every Employee currently in its collection.

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Department engineering = new Department("Engineering");
engineering.addEmployee(new Employee("Karan Mehta"));
engineering.addEmployee(new Employee("Sneha Rao"));
session.persist(engineering); // cascades to both employees
tx.commit();
session.close();
Console Output

Click Run to see what this code prints.

Fetching Related Entities

Loading a Department gives you access to its employees collection. By default, @OneToMany is LAZY, so the employees are only fetched from the database the moment you actually access the collection.

Session session = sessionFactory.openSession();
Department department = session.get(Department.class, 1L);
System.out.println("Department: " + department.getName());
for (Employee e : department.getEmployees()) { // triggers the lazy fetch here
System.out.println(" - " + e.getName());
}
session.close();
Console Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Forgetting mappedBy on @OneToMany, which causes Hibernate to try to create a separate join table instead of reusing the department_id foreign key.
  • Adding an Employee only to the collection without setting employee.setDepartment(this), leaving the foreign key null on save.
  • Accessing department.getEmployees() outside an open Session and hitting a LazyInitializationException.
  • Using CascadeType.REMOVE carelessly, deleting an entire department's employees when only the department record should have been removed.

Best Practices

  • Add a convenience method like addEmployee() to keep both sides of the relationship synchronized.
  • Put @JoinColumn on the @ManyToOne side — it always owns the foreign key in this relationship type.
  • Use orphanRemoval = true when a child (Employee) should never exist without its parent (Department).
  • Leave @OneToMany collections as LAZY (the default) unless you specifically need eager loading, to avoid pulling large collections unnecessarily.

Frequently Asked Questions

Yes — you can put @OneToMany on Department with a @JoinColumn instead of mappedBy, without an Employee-side back-reference at all. It works, but it is less efficient because Hibernate issues an extra UPDATE to set the foreign key after inserting the child.

When true, removing an Employee from department.getEmployees() and flushing the session deletes that Employee row from the database entirely, not just unlinks it from the department.

Because the "many" side is where each individual row needs to reference exactly one parent — the foreign key column lives on the table with many rows, which is the employees table here.

Key Takeaways

  • @OneToMany and @ManyToOne are two views of the same relationship, from opposite sides.
  • The @ManyToOne side always owns the foreign key column via @JoinColumn.
  • The @OneToMany side is the inverse side and must declare mappedBy.
  • Keep both sides of a bidirectional relationship in sync using a helper method.

Summary

One-to-many relationships appear constantly in real schemas, and the owning-side rules here are identical in spirit to one-to-one. Next, you will handle relationships where both sides can have many related rows on either end, using @ManyToMany.

Next Lesson →

Many-to-Many Mappings