Chapter 4.4☕ 18 min read

Entity Relationships (@OneToMany, @ManyToOne)

Database relationships mapped to Java annotations.

01The Concept: Foreign Keys & Relationships

In real databases, tables are not isolated. They are connected. A Department has many Employees. A User has one Profile. Spring Data JPA handles these connections using relationship annotations.

The Hyderabad Shopping Mall Analogy:

Imagine a massive mall in Banjara Hills. Inside the mall, there are multiple shops (A One-to-Many relationship: Mall → Shops). Each shop has a signboard saying "Belongs to Banjara Hills Mall". This signboard is the Foreign Key.

In JPA, @ManyToOne puts the foreign key on the "Many" side (the Shop), and @OneToMany tells the "One" side (the Mall) that it has a collection of shops.

02Technical Explanation
  1. @ManyToOne: The owner of the relationship. This is where the Foreign Key actually lives in the database.
  2. @OneToMany(mappedBy = "department"): The inverse side. It tells Hibernate: "Don't create a new table for this. Look at the department field in the Employee class to find the connection."
  3. Fetch Types:
    FetchType.EAGER (Default for To-One): Fetches the related data immediately in the same SQL query.
    FetchType.LAZY (Default for To-Many): Waits to fetch the data until you actually call the getter method. (Crucial for performance).
03Full Working Code: One-to-Many Relationship

Let's create a Department and Employee relationship.

package com.devinhyderabad;

import jakarta.persistence.*;
import java.util.List;

// --- ONE SIDE ---
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

// mappedBy = "department" means the Employee class owns the foreign key.
// FetchType.LAZY means don't fetch employees unless explicitly asked.
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
private List<Employee> employees;

public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List<Employee> getEmployees() { return employees; }
public void setEmployees(List<Employee> employees) { this.employees = employees; }
}
04The Employee Entity (Many Side)
// --- MANY SIDE ---
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

// @ManyToOne is where the Foreign Key column is created in the DB.
// @JoinColumn specifies the name of the foreign key column.
@ManyToOne
@JoinColumn(name = "department_id")
private Department department;

public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
}
05Why It Matters / Interview Note

Interview Question: "What is the difference between @OneToMany and @ManyToOne? Which one owns the relationship?"

Answer: @ManyToOne always owns the relationship because it physically stores the Foreign Key in the database table. @OneToMany is the inverse side and uses mappedBy to point back to the owner. You must always define the mappedBy attribute on the @OneToMany side, otherwise JPA will generate an extra mapping table.

Enterprise Note: The biggest trap in JPA is the LazyInitializationException. If you fetch a Department with LAZY employees, and try to access department.getEmployees() after the database session (transaction) has closed, Hibernate will crash. This is why we use DTOs to map data while the session is open.

Key Takeaways

  • ✅ @ManyToOne is the relationship owner — stores the Foreign Key
  • ✅ @OneToMany(mappedBy = "...") is the inverse side — avoids extra join tables
  • ✅ FetchType.LAZY improves performance for collection relationships
  • ✅ Beware of LazyInitializationException — access lazy fields within the transaction