✅ Short Answer
To map a many-to-many relationship with extra columns in the join table, you can’t use plain @ManyToMany
.
Instead, you must model the join table as a separate entity with two @ManyToOne
relationships — this is called an association entity (or link entity).
🔎 Detailed Explanation
🔹 Why doesn’t @ManyToMany
work?
- JPA’s
@ManyToMany
creates a join table, but doesn’t let you add extra columns — it only maps the two foreign keys. - As soon as you need attributes in the join table (e.g., quantity, timestamp), you must model it explicitly.
🧑💻 Example: Students enrolled in Courses
You want to store enrollmentDate
in the join table between Student
and Course
.
@Entity
public class Enrollment {
@EmbeddedId
private EnrollmentId id;
@ManyToOne
@MapsId("studentId")
private Student student;
@ManyToOne
@MapsId("courseId")
private Course course;
private LocalDate enrollmentDate; // extra column!
}
Composite Key for Enrollment
@Embeddable
public class EnrollmentId implements Serializable {
private Long studentId;
private Long courseId;
// equals() and hashCode() required!
}
Entities with mapped enrollments
@Entity
public class Student {
@Id
private Long id;
@OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Enrollment> enrollments = new ArrayList<>();
}
@Entity
public class Course {
@Id
private Long id;
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Enrollment> enrollments = new ArrayList<>();
}
🔹 What does this give you?
✅ A join table represented by Enrollment
with:
student_id
(FK)course_id
(FK)enrollment_date
(extra column)
✅ Ability to attach attributes to the relationship → essential for real-world scenarios.
📊 Benefits of association entity approach
✅ Adds flexibility → store relationship metadata.
✅ Supports cascade operations & orphan removal on relationships.
✅ Allows proper object modeling → no awkward DTO hacks.
📌 Key Takeaways
✅ You can’t use @ManyToMany
directly with extra columns → must create an association entity.
✅ Map the association entity with @ManyToOne
+ @EmbeddedId
(or @IdClass
) for composite keys.
✅ This pattern gives you full power over join table attributes.