π 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.
Top comments (0)