🚀 Many-to-Many Relationship in Hibernate
In a Many-to-Many relationship:
- An entity can have multiple related entities, and vice versa.
- A join table is required to store the relationships.
We’ll go through both unidirectional and bidirectional mappings using Student
and Course
entities.
1️⃣ Unidirectional @ManyToMany
✅ In a unidirectional Many-to-Many:
- Only one entity knows about the relationship.
- The foreign key mapping is stored in a join table.
📌 Example: A Student
can enroll in many Courses
, but Course
doesn’t reference Student
.
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "student_course", // ✅ Join table name
joinColumns = @JoinColumn(name = "student_id"), // ✅ FK for Student
inverseJoinColumns = @JoinColumn(name = "course_id") // ✅ FK for Course
)
private List<Course> courses = new ArrayList<>();
}
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
}
✅ Only Student
knows about Course
, and Course
has no reference back.
🔹 Generated SQL (Creates a Join Table)
CREATE TABLE student (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE course (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255)
);
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)
);
✅ The student_course
table links students to courses.
📌 Saving Data
Course course1 = new Course();
course1.setTitle("Mathematics");
Course course2 = new Course();
course2.setTitle("Physics");
Student student = new Student();
student.setName("John Doe");
student.getCourses().add(course1);
student.getCourses().add(course2);
entityManager.persist(course1);
entityManager.persist(course2);
entityManager.persist(student);
🚀 The student is now enrolled in two courses!
📌 Querying Data
Student student = entityManager.find(Student.class, 1L);
List<Course> courses = student.getCourses();
courses.forEach(course -> System.out.println(course.getTitle())); // ✅ Works!
✅ You can query courses from a student, but not the other way around.
2️⃣ Bidirectional @ManyToMany
✅ In a bidirectional Many-to-Many:
- Both entities reference each other.
- One entity is the owning side (
@JoinTable
). - The other entity is the inverse side (
mappedBy
).
📌 Example: A Student
can enroll in many Courses
, and a Course
can have many Students
.
Owning Side (Student
) - Uses @JoinTable
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "student_course", // ✅ Join table
joinColumns = @JoinColumn(name = "student_id"), // ✅ FK for Student
inverseJoinColumns = @JoinColumn(name = "course_id") // ✅ FK for Course
)
private List<Course> courses = new ArrayList<>();
}
Inverse Side (Course
) - Uses mappedBy
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@ManyToMany(mappedBy = "courses") // ✅ References the field in Student
private List<Student> students = new ArrayList<>();
}
✅ mappedBy = "courses"
tells Hibernate:
- "The join table is already defined in
Student.courses
." - "Don’t create another join table in
Course
."
🔹 Generated SQL (No Duplicate Join Table!)
CREATE TABLE student (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE course (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255)
);
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)
);
✅ The student_course
table is correctly mapped without duplication.
📌 Saving Data (Same as Before)
Course course1 = new Course();
course1.setTitle("Mathematics");
Course course2 = new Course();
course2.setTitle("Physics");
Student student = new Student();
student.setName("John Doe");
student.getCourses().add(course1);
student.getCourses().add(course2);
course1.getStudents().add(student); // ✅ Add student to course
course2.getStudents().add(student);
entityManager.persist(course1);
entityManager.persist(course2);
entityManager.persist(student);
✅ Now, both Student
and Course
are correctly linked.
📌 Querying Both Directions
✅ Get Courses from Student
Student student = entityManager.find(Student.class, 1L);
List<Course> courses = student.getCourses();
courses.forEach(course -> System.out.println(course.getTitle())); // ✅ Works!
✅ Get Students from Course
Course course = entityManager.find(Course.class, 1L);
List<Student> students = course.getStudents();
students.forEach(student -> System.out.println(student.getName())); // ✅ Works!
✅ Unlike the unidirectional version, now you can access both Student → Course and Course → Student.
3️⃣ Summary: Unidirectional vs. Bidirectional @ManyToMany
Feature | Unidirectional (@ManyToMany ) |
Bidirectional (@ManyToMany + mappedBy ) |
---|---|---|
@ManyToMany used? |
✅ Yes | ✅ Yes (Both Sides) |
@JoinTable used? |
✅ Yes (Owning Side) | ✅ Yes (Only in Owning Side) |
mappedBy used? |
❌ No | ✅ Yes (Inverse Side) |
Extra join table? | ✅ Yes | ✅ Yes (But Correctly Shared) |
Reference back? | ❌ No | ✅ Yes (Both Can Access Each Other) |
✅ Best Practice: Use bidirectional @ManyToMany
if you need to query both ways.
🚀 Unidirectional @ManyToMany
is simpler if you only query in one direction.
🎯 Final Takeaways
-
Unidirectional
@ManyToMany
= Only one entity knows about the other (@JoinTable
in the owning side). -
Bidirectional
@ManyToMany
= Both entities reference each other (mappedBy
used on the inverse side). - Use bidirectional when both sides need to access each other.
- Always place the
@JoinTable
annotation on the owning side.