3. Relationship Mappings

1. @OneToOne

About

  • @OneToOne defines a one-to-one relationship between two entity classes.

  • It means each instance of Entity A is associated with exactly one instance of Entity B, and vice versa.

  • It is bidirectional or unidirectional based on how we model the relationship.

Example - Employee and EmployeeDetails

  • Each Employee has one EmployeeDetails record.

  • Each EmployeeDetails is linked to exactly one Employee.

Syntax

@Entity
public class Employee {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToOne
    private EmployeeDetails details;
}

This maps a simple unidirectional @OneToOne — Employee has a link to EmployeeDetails.

Attributes

Attribute
Description

mappedBy

Indicates the inverse side of the relationship (used in bidirectional mapping).

cascade

Defines cascade operations like persist, remove, merge etc.

fetch

Defines when to fetch related entity: EAGER (default) or LAZY.

optional

Whether the relationship is mandatory (false) or optional (true). Default is true.

orphanRemoval

Whether to automatically remove orphaned records.

How Relationships Are Stored in Database

  • Usually through a foreign key.

  • Typically one table has a foreign key reference to the other table.

For example:

create table employee (
    id bigint not null,
    name varchar(255),
    details_id bigint,
    primary key (id),
    foreign key (details_id) references employee_details(id)
);

create table employee_details (
    id bigint not null,
    address varchar(255),
    phone_number varchar(255),
    primary key (id)
);

1. Unidirectional @OneToOne

Only one entity knows about the relationship.

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "details_id", referencedColumnName = "id")
    private EmployeeDetails details;
}
@Entity
public class EmployeeDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String address;
    private String phoneNumber;
}
  • @JoinColumn(name = "details_id", referencedColumnName = "id")

    • details_id column in employee table holds the foreign key referencing employee_details(id).

  • cascade = CascadeType.ALL

    • If we persist or delete an Employee, EmployeeDetails will automatically be persisted or deleted too.

2. Bidirectional @OneToOne

Both entities are aware of the relationship.

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToOne(mappedBy = "employee", cascade = CascadeType.ALL)
    private EmployeeDetails details;
}
@Entity
public class EmployeeDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String address;
    private String phoneNumber;

    @OneToOne
    @JoinColumn(name = "employee_id", referencedColumnName = "id")
    private Employee employee;
}
  • EmployeeDetails owns the relationship (@JoinColumn is there).

  • Employee just references it through mappedBy = "employee".

  • mappedBy tells JPA that the foreign key is managed by the other side (i.e., EmployeeDetails).

Cascade Types

We often want operations on the parent to affect the child entity too. Typical cascade types with @OneToOne:

  • CascadeType.PERSIST: Save parent ➔ saves child.

  • CascadeType.REMOVE: Delete parent ➔ deletes child.

  • CascadeType.MERGE: Merge parent ➔ merges child.

  • CascadeType.ALL: All operations (persist, merge, remove, refresh, detach).

FetchType

  • FetchType.EAGER (Default): Child entity is loaded immediately with parent entity.

  • FetchType.LAZY: Child entity is loaded only when accessed.

In @OneToOne, default is EAGER (unlike @OneToMany where it’s LAZY).

2. @OneToMany

About

  • @OneToMany defines a one-to-many relationship between two entity classes.

  • It means one instance of Entity A is associated with multiple instances of Entity B.

  • For example, one Department has many Employees.

Syntax

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany
    private List<Employee> employees;
}

This is a unidirectional mapping — Department has a list of Employees.

Attributes

Attribute
Description

mappedBy

Specifies that this side is the inverse side (non-owning side).

cascade

Propagate operations (persist, merge, remove, etc.).

fetch

LAZY (default) or EAGER fetching behavior.

orphanRemoval

Automatically remove child records if they are no longer referenced.

How It Is Mapped in Database ?

Usually with a foreign key in the child table.

create table department (
    id bigint not null,
    name varchar(255),
    primary key (id)
);

create table employee (
    id bigint not null,
    name varchar(255),
    department_id bigint,
    primary key (id),
    foreign key (department_id) references department(id)
);
  • The employee table holds a foreign key (department_id) pointing to department(id).

1. Unidirectional @OneToMany

Only one side (Department) knows about the relationship. But foreign key is still in child table (Employee).

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "department_id") // Important
    private List<Employee> employees = new ArrayList<>();
}
@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}
  • @JoinColumn(name = "department_id") tells Hibernate to use department_id in the Employee table as a foreign key.

  • Without @JoinColumn, Hibernate would create an unnecessary join table (bad for performance if not needed).

  • cascade = CascadeType.ALL will propagate persist, remove, etc.

2. Bidirectional @OneToMany and @ManyToOne

Both sides know the relationship.

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Employee> employees = new ArrayList<>();
}
@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;
}
  • Department is the inverse side (mappedBy = "department").

  • Employee owns the foreign key (@JoinColumn(name = "department_id")).

  • orphanRemoval = true means if we remove an Employee from Department's employee list, Hibernate deletes it.

Common cascade types:

  • CascadeType.PERSIST: When saving Department, Employees are saved automatically.

  • CascadeType.REMOVE: Deleting Department deletes Employees.

  • CascadeType.MERGE: Merging Department merges Employees.

  • CascadeType.ALL: All operations cascaded.

Fetch Type

FetchType.LAZY (default) -> Child entities (List<Employee>) are loaded only when accessed.

FetchType.EAGER -> Child entities are loaded immediately with parent entity.

  • @OneToMany is default LAZY.

  • Be careful while changing it to EAGER — it can cause performance issues if there are lots of child records.

Orphan Removal

  • Setting orphanRemoval = true automatically deletes Employees that are removed from the Department’s list.

department.getEmployees().remove(employee);

Hibernate will DELETE that Employee from DB.

3. @ManyToOne

About

  • @ManyToOne defines a many-to-one relationship between two entities.

  • It means many instances of Entity A are associated with one instance of Entity B.

  • Example: Many Employees belong to one Department.

Syntax

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    private Department department;
}

This is a simple way to map Employee ➔ Department.

Attributes

Attribute
Description

fetch

Defines loading strategy: EAGER (default) or LAZY.

optional

Defines if the association is mandatory (false) or optional (true).

cascade

Defines cascading of operations (persist, merge, remove, etc.).

targetEntity

Defines the class type explicitly if necessary (rarely used).

How It Is Mapped in Database ?

  • The foreign key is created in the table of the owning entity (Employee).

create table department (
    id bigint not null,
    name varchar(255),
    primary key (id)
);

create table employee (
    id bigint not null,
    name varchar(255),
    department_id bigint,
    primary key (id),
    foreign key (department_id) references department(id)
);
  • employee.department_id points to department.id.

Entity Classes

@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}
@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "department_id")  // Explicit foreign key column name
    private Department department;
}
  • @ManyToOne is placed on the field in the owning entity (Employee).

  • @JoinColumn(name = "department_id") maps the foreign key column.

  • fetch = FetchType.LAZY means Department will not be loaded until needed.

  • optional = false means the Employee must have a Department (null not allowed).

We can set cascade operations:

@ManyToOne(cascade = CascadeType.ALL)
private Department department;

But careful — usually we don’t cascade from child to parent! (We don't want saving Employee to randomly save/update Department.)

Best Practice: Avoid cascade for @ManyToOne unless there is a strong reason.

Fetch Type

FetchType.EAGER (default) -> Always loads the Department with the Employee.

FetchType.LAZY -> Loads the Department only when accessed.

Note:

  • In @ManyToOne, default is EAGER (unlike @OneToMany where default is LAZY).

  • It is recommended to manually set it to LAZY for large data models to avoid performance issues.

Optional Attribute

optional = true (default) -> Employee can exist without a Department (foreign key can be null).

optional = false -> Department is required for Employee (foreign key must not be null).

If we set optional = false, Hibernate generates NOT NULL constraint on department_id in employee table.

4. @ManyToMany

About

  • @ManyToMany defines a many-to-many relationship between two entities.

  • It means many instances of Entity A are related to many instances of Entity B.

  • Example: Many Employees can work on many Projects.

Syntax

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany
    private List<Course> courses;
}

This creates a many-to-many relation between Student and Course

Attributes

Attribute
Description

fetch

Defines loading strategy: LAZY (default) or EAGER.

cascade

Defines cascade operations on associated entities.

targetEntity

Explicitly defines associated class if needed.

How It Is Mapped in Database ?

  • JPA creates an intermediate join table to manage the association.

create table student (
    id bigint not null,
    name varchar(255),
    primary key (id)
);

create table course (
    id bigint not null,
    title varchar(255),
    primary key (id)
);

create table student_course (
    student_id bigint not null,
    course_id bigint not null,
    primary key (student_id, course_id),
    foreign key (student_id) references student(id),
    foreign key (course_id) references course(id)
);

@ManyToMany always requires an intermediate join table because relational databases do not support direct many-to-many relations.

The join table holds two foreign keys:

  • One pointing to Entity A.

  • One pointing to Entity B.

The join table can optionally have a composite primary key (both foreign keys together).

1. Unidirectional @ManyToMany Mapping

  • One Entity knows about the other.

  • The association is one-way.

  • Only the owning entity manages the relationship.

  • Other entity (target entity) does NOT know about the relation.

@Entity
public class Student {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses = new ArrayList<>();
}
@Entity
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
}

Generated Database Tables

Table
Columns
Primary Key
Foreign Keys

student

id, name

id

-

course

id, title

id

-

student_course

student_id, course_id

(student_id, course_id) composite key

FK (student_id) → student(id) FK (course_id) → course(id)

  • Join Table (student_course) is fully managed by Student.

  • Course does not know anything about Students.

Important

  • Since Course does not have any @ManyToMany(mappedBy = ...), it is unaware of this relationship in Java model.

2. Bidirectional @ManyToMany Mapping

  • Both Entities know about each other.

  • Association is two-way.

  • One side is the owning side (@JoinTable), and the other is the inverse side (mappedBy).

  • Only the owning side actually updates the join table in database.

  • In Java memory, both sides need to be manually synchronized.

@Entity
public class Student {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses = new ArrayList<>();
}
@Entity
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToMany(mappedBy = "courses")
    private List<Student> students = new ArrayList<>();
}

Generated Database Tables

Table
Columns
Primary Key
Foreign Keys

student

id, name

id

-

course

id, title

id

-

student_course

student_id, course_id

(student_id, course_id) composite key

FK (student_id) → student(id) FK (course_id) → course(id)

  • Same join table structure as unidirectional.

  • But now, both entities are aware of the relationship.

  • Hibernate still only uses the owning side (Student) to insert/update/delete rows in the join table.

Fetch Type

FetchType.LAZY (default) -> Load related entities when accessed.

FetchType.EAGER -> Load related entities immediately.

Best Practice:

  • Keep ManyToMany as LAZY.

  • EAGER loading on large many-to-many relations can explode query size and cause serious performance problems.

Cascade operations

@ManyToMany(cascade = CascadeType.PERSIST)
private List<Course> courses;

But in real-world apps:

  • Cascade cautiously.

  • Usually, you don’t cascade on @ManyToMany because both sides can exist independently.

Owning Side vs Inverse Side

Owning Side -> The entity that defines @JoinTable or @JoinColumn.

Inverse Side -> The entity with mappedBy.

  • Only the owning side is responsible for updating the join table.

  • The inverse side just reflects the relationship.

5. @JoinColumn

About

  • @JoinColumn is used to customize the foreign key column generated for an entity association (like @OneToOne, @ManyToOne, etc.).

  • It defines how two tables are joined in the database by explicitly specifying:

    • The foreign key column name.

    • Referenced primary key column.

  • Without @JoinColumn, JPA uses default naming conventions (which can be confusing or inconsistent).

Where is @JoinColumn Used ?

  • It is placed on the owning side of relationships:

    • @OneToOne

    • @ManyToOne

    • (Optionally) in @OneToMany + @JoinColumn (to avoid a join table)

  • Also used in @JoinTable (for join table’s joinColumns and inverseJoinColumns).

Syntax of @JoinColumn

@JoinColumn(
    name = "foreign_key_column_name",
    referencedColumnName = "primary_key_column_name",
    nullable = true/false,
    unique = true/false,
    insertable = true/false,
    updatable = true/false,
    foreignKey = @ForeignKey(name = "fk_constraint_name")
)

Composite Foreign Keys (Multiple @JoinColumns)

If we need multiple columns (composite foreign key), use @JoinColumns:

@ManyToOne
@JoinColumns({
    @JoinColumn(name = "dept_id", referencedColumnName = "id"),
    @JoinColumn(name = "dept_code", referencedColumnName = "code")
})
private Department department;

Important Parameters

Parameter
Meaning

name

Name of the foreign key column in the owner’s table.

referencedColumnName

Name of the primary key column in the target entity. Default: "id".

nullable

Whether the foreign key can be NULL (default: true).

unique

Whether the foreign key must be UNIQUE.

insertable

Whether the foreign key column is included in SQL INSERT (default: true).

updatable

Whether the foreign key column is included in SQL UPDATE (default: true).

foreignKey

Specifies foreign key constraint name in database.

Example — @ManyToOne with @JoinColumn

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;

    @ManyToOne
    @JoinColumn(name = "department_id", referencedColumnName = "id")
    private Department department;
}
@Entity
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String departmentName;
}

Database Tables Generated:

Table
Columns

department

id (PK), department_name

employee

id (PK), name, department_id (FK to department.id)

  • @JoinColumn(name = "department_id") tells JPA to create a foreign key column named department_id in Employee table.

  • It references the id column of Department table.

If @JoinColumn is not given explicitly:

  • Hibernate defaults to <association_property>_<referenced_primary_key>.

  • In above example: department_id would still happen automatically — but being explicit is better for clarity and control.

How @JoinColumn behaves if missing ?

  • JPA will:

    • Generate a foreign key.

    • Guess a column name using entity field name and primary key.

    • Constraint name will be auto-generated.

It is considered good practice to always specify @JoinColumn manually for:

  • Database readability.

  • Easier migrations.

  • Predictable schema.

6. @JoinTable

About

  • @JoinTable is used to define the Join Table (i.e., intermediate table) when two entities have a many-to-many or sometimes one-to-many association.

  • A Join Table is a separate table that holds foreign keys referencing the primary keys of the two associated tables.

  • @JoinTable gives you full control over:

    • The name of the intermediate table.

    • The join columns (owner side foreign key).

    • The inverse join columns (other side foreign key).

Where @JoinTable is Used ?

  • Mostly in @ManyToMany mappings.

  • Sometimes in @OneToMany (only if you want to manage the relation via a separate table instead of a foreign key directly).

Syntax of @JoinTable

@JoinTable(
    name = "join_table_name",
    joinColumns = @JoinColumn(name = "owning_entity_foreign_key"),
    inverseJoinColumns = @JoinColumn(name = "inverse_entity_foreign_key")
)

Parameters

Parameter
Meaning

name

Name of the join table in DB.

joinColumns

Specifies the column that refers to the current (owning) entity’s primary key.

inverseJoinColumns

Specifies the column that refers to the other (inverse) entity’s primary key.

uniqueConstraints

Optional. Defines uniqueness constraints on join table columns.

indexes

Optional. Allows creating indexes on join table columns.

Example — @ManyToMany using @JoinTable

Suppose we have two entities:

  • Employee

  • Project

An employee can work on multiple projects, and a project can have multiple employees.

Entity Classes:

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;

    @ManyToMany
    @JoinTable(
        name = "employee_project",
        joinColumns = @JoinColumn(name = "employee_id"),
        inverseJoinColumns = @JoinColumn(name = "project_id")
    )
    private List<Project> projects;
}
@Entity
public class Project {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String projectName;
}

Database Structure Generated

Table
Columns

employee

id (PK), name

project

id (PK), project_name

employee_project

employee_id (FK to employee.id), project_id (FK to project.id)

The employee_project table acts as a bridge table — no direct foreign key inside employee or project tables.

Ownership in @JoinTable

  • The entity where @JoinTable is defined is the owning side.

  • The other entity is inverse side (no control over join table).

In example above:

  • Employee owns the relationship.

  • Project does not.

Last updated

Was this helpful?