Skip to content

Commit b71a715

Browse files
committed
HHH-11026 Test cascade merge many-to-many leading to exception
1 parent 76359ad commit b71a715

File tree

4 files changed

+48
-4
lines changed

4 files changed

+48
-4
lines changed

hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Course.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
package org.hibernate.orm.test.annotations.idmanytoone;
66

77
import java.io.Serializable;
8+
import java.util.HashSet;
89
import java.util.Set;
10+
11+
import jakarta.persistence.CascadeType;
912
import jakarta.persistence.Entity;
1013
import jakarta.persistence.GeneratedValue;
1114
import jakarta.persistence.Id;
@@ -25,8 +28,8 @@ public class Course implements Serializable {
2528

2629
private String name;
2730

28-
@OneToMany(mappedBy = "course")
29-
private Set<CourseStudent> students;
31+
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL)
32+
private Set<CourseStudent> students = new HashSet<>();
3033

3134
public Course() {
3235
}

hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/CourseStudent.java

+2
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
import jakarta.persistence.JoinColumn;
1313
import jakarta.persistence.ManyToOne;
1414
import jakarta.persistence.Table;
15+
import org.hibernate.annotations.processing.Exclude;
1516

1617
/**
1718
* @author Alex Kalashnikov
1819
*/
1920
@Entity
2021
@Table(name = "idmanytoone_course_student")
22+
@Exclude // Avoid generating an IdClass through the annotation processor. See https://hibernate.atlassian.net/browse/HHH-18829
2123
public class CourseStudent implements Serializable {
2224

2325
@Id

hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/IdManyToOneTest.java

+36
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl;
1515
import org.hibernate.cfg.Configuration;
1616

17+
import org.hibernate.testing.orm.junit.Jira;
1718
import org.hibernate.testing.orm.junit.JiraKey;
1819
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
1920
import org.junit.Test;
@@ -78,6 +79,41 @@ public void testCriteriaRestrictionOnIdManyToOne() {
7879
} );
7980
}
8081

82+
@Test
83+
@Jira("https://hibernate.atlassian.net/browse/HHH-11026")
84+
public void testMerge() {
85+
inTransaction( s-> {
86+
Student student = new Student();
87+
student.setName( "s1" );
88+
Course course = new Course();
89+
course.setName( "c1" );
90+
s.persist( student );
91+
s.persist( course );
92+
93+
CourseStudent courseStudent = new CourseStudent();
94+
courseStudent.setStudent( student );
95+
courseStudent.setCourse( course );
96+
student.getCourses().add( courseStudent );
97+
course.getStudents().add( courseStudent );
98+
s.merge( student );
99+
100+
// Merge will cascade Student#courses and replace the CourseStudent instance within,
101+
// but the original CourseStudent is still contained in Student#courses that will be cascaded on flush,
102+
// which is when the NonUniqueObjectException is thrown, because at that point,
103+
// two CourseStudent objects with the same primary key exist.
104+
// This can be worked around by replacing the original CourseStudent with the merged on as hinted below,
105+
// but I'm not sure if copying the CourseStudent instance on merge really makes sense,
106+
// since the load for the merge showed that there is no row for that key in the database.
107+
// I tried avoiding the copy in org.hibernate.event.internal.DefaultMergeEventListener#copyEntity
108+
// which also required updating the child-parent state in StatefulPersistenceContext to point to
109+
// the new parent according to the MergeContext. This mostly worked, but required further investigation
110+
// to fix a few failing tests. This copy on merge topic needs to be discussed further before continuing.
111+
112+
// course.getStudents().remove( courseStudent );
113+
// course.getStudents().add( student.getCourses().iterator().next() );
114+
} );
115+
}
116+
81117
@Override
82118
protected Class[] getAnnotatedClasses() {
83119
return new Class[] {

hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Student.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
package org.hibernate.orm.test.annotations.idmanytoone;
66

77
import java.io.Serializable;
8+
import java.util.HashSet;
89
import java.util.Set;
10+
11+
import jakarta.persistence.CascadeType;
912
import jakarta.persistence.Entity;
1013
import jakarta.persistence.GeneratedValue;
1114
import jakarta.persistence.Id;
@@ -25,8 +28,8 @@ public class Student implements Serializable {
2528

2629
private String name;
2730

28-
@OneToMany(mappedBy = "student")
29-
private Set<CourseStudent> courses;
31+
@OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
32+
private Set<CourseStudent> courses = new HashSet<>();
3033

3134
public Student() {
3235
}

0 commit comments

Comments
 (0)