I have 3 tables in my database: Students
, Courses
and Students_Courses
Students can have multiple courses and courses can have multiple students. There is a many-to-many relationship between Students
and Courses
.
I have 3 cases for my project and courses added to my Courses
table.
User_Courses
- again, expected behaviour.Students
and Students_Courses
, but it is also deleting Courses
records which is not required. Even if I don't have any user in a course, I want the course to be there. Below is my code for tables and annotate classes.
CREATE TABLE `Students` (
`StudentID` INT(11) NOT NULL AUTO_INCREMENT,
`StudentName` VARCHAR(50) NOT NULL
PRIMARY KEY (`StudentID`)
)
CREATE TABLE `Courses` (
`CourseID` INT(11) NOT NULL AUTO_INCREMENT,
`CourseName` VARCHAR(50) NOT NULL
PRIMARY KEY (`CourseID`)
)
CREATE TABLE `Student_Courses` (
`StudentId` INT(10) NOT NULL DEFAULT '0',
`CourseID` INT(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`StudentId`, `CourseID`),
INDEX `FK__courses` (`CourseID`),
INDEX `StudentId` (`StudentId`),
CONSTRAINT `FK__courses` FOREIGN KEY (`CourseID`) REFERENCES `courses` (`CourseID`) ON DELETE NO ACTION,
CONSTRAINT `FK_students` FOREIGN KEY (`StudentId`) REFERENCES `students` (`StudentId`)
)
This is the Java code generated by Hibernate:
@Entity
@Table(name = "Students")
public class Students implements java.io.Serializable {
private Integer StudentID;
private String Students;
private Set<Courses> Courseses = new HashSet<Courses>(0);
public Students() {
}
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "StudentID", unique = true, nullable = false)
public Integer getStudentID() {
return this.StudentID;
}
public void setStudentID(Integer StudentID) {
this.StudentID = StudentID;
}
@Column(name = "Students", nullable = false, length = 50)
public String getCampaign() {
return this.Students;
}
public void setCampaign(String Students) {
this.Students = Students;
}
@ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
@JoinTable(name = "Student_Courses", joinColumns = {
@JoinColumn(name = "StudentId", nullable = false, updatable = false)}, inverseJoinColumns = {
@JoinColumn(name = "CourseID", nullable = false, updatable = false)})
public Set<Courses> getCourseses() {
return this.Courseses;
}
public void setCourseses(Set<Courses> Courseses) {
this.Courseses = Courseses;
}
}
@Entity
@Table(name = "Courses")
public class Courses implements java.io.Serializable {
private Integer CourseID;
private String CourseName;
private Set<Students> Studentses = new HashSet<Students>(0);
public Courses() {
}
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "CourseID", unique = true, nullable = false)
public Integer getCourseID() {
return this.CourseID;
}
public void setCourseID(Integer CourseID) {
this.CourseID = CourseID;
}
@Column(name = "CourseName", nullable = false, length = 100)
public String getCourseName() {
return this.CourseName;
}
public void setCourseName(String CourseName) {
this.CourseName = CourseName;
}
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "Courseses")
public Set<Students> getStudentses() {
return this.Studentses;
}
public void setStudentses(Set<Students> Studentses) {
this.Studentses = Studentses;
}
}
How can I achieve what I have described? I could not find any reasonable documentation on the web.
You don't need to use bi-directional association instead of your code, you have just to add CascaType. Remove as a property to ManyToOne annotation, then use @OnDelete(action = OnDeleteAction.
Solution: Yes, the JPA specification provides the orphanRemoval feature for these use cases. When you activate it on the association, Hibernate removes a child entity when you remove its association to the parent entity.
CascadeType. REMOVE : It means that the related entities are deleted when the owning entity is deleted. CascadeType. DETACH : It detaches all the related entities if a manual detach occurs.
I found the correct mapping (and tested that with JUnit with an extensive case) in a similar scenario. I don't think I am going to post testing code because it would take long time to adapt to this example. Anyway the key is to:
mappedBy
attribute for the annotations, use join columnsCascadeTypes
excluding REMOVE
In OP's example
@ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Course.class)
@JoinTable(name = "XTB_STUDENTS_COURSES",
inverseJoinColumns = @JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
joinColumns = @JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = @ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Course> courses = new HashSet<>();
@ManyToMany(fetch = FetchType.LAZY,
cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
},
targetEntity = Student.class)
@JoinTable(name = "XTB_STUDENTS_COURSES",
joinColumns = @JoinColumn(name = "COURSE_ID",
nullable = false,
updatable = false),
inverseJoinColumns = @JoinColumn(name = "STUDENT_ID",
nullable = false,
updatable = false),
foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT),
inverseForeignKey = @ForeignKey(ConstraintMode.CONSTRAINT))
private final Set<Student> students = new HashSet<>();
Extensive JUnit testing verified that:
Based on what you've told me you don't want cascade=CascadeType.ALL on the getCourseses method in Student. Keep in mind that Hibernate cascades are not the same as database cascades. Even if you don't have any cascades then Hibernate will delete the Students_Courses record.
The best way to think of Hibernate cascades is that if you call an operation on an entity and that operation is listed in the cascade list then that operation will be called on all of the child entities.
For example, when you call delete on Student, since delete is in the cascade list for Courses, Hibernate will call delete on each of the Course entities referenced by that student. That is why you are seeing the Course records disappearing.
Don't worry about database cascades, Hibernate will take care of those on its own.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With