diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Course.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Course.java index fc5e879ae500..06c8d6d8cf35 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Course.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Course.java @@ -5,7 +5,10 @@ package org.hibernate.orm.test.annotations.idmanytoone; import java.io.Serializable; +import java.util.HashSet; import java.util.Set; + +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -25,8 +28,8 @@ public class Course implements Serializable { private String name; - @OneToMany(mappedBy = "course") - private Set students; + @OneToMany(mappedBy = "course", cascade = CascadeType.ALL) + private Set students = new HashSet<>(); public Course() { } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/CourseStudent.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/CourseStudent.java index a21f7787a141..2cb2b5421f7e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/CourseStudent.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/CourseStudent.java @@ -12,12 +12,14 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import org.hibernate.annotations.processing.Exclude; /** * @author Alex Kalashnikov */ @Entity @Table(name = "idmanytoone_course_student") +@Exclude // Avoid generating an IdClass through the annotation processor. See https://hibernate.atlassian.net/browse/HHH-18829 public class CourseStudent implements Serializable { @Id diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/IdManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/IdManyToOneTest.java index df26e20bd261..756206209bed 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/IdManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/IdManyToOneTest.java @@ -14,6 +14,7 @@ import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; import org.hibernate.cfg.Configuration; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; @@ -78,6 +79,41 @@ public void testCriteriaRestrictionOnIdManyToOne() { } ); } + @Test + @Jira("https://hibernate.atlassian.net/browse/HHH-11026") + public void testMerge() { + inTransaction( s-> { + Student student = new Student(); + student.setName( "s1" ); + Course course = new Course(); + course.setName( "c1" ); + s.persist( student ); + s.persist( course ); + + CourseStudent courseStudent = new CourseStudent(); + courseStudent.setStudent( student ); + courseStudent.setCourse( course ); + student.getCourses().add( courseStudent ); + course.getStudents().add( courseStudent ); + s.merge( student ); + + // Merge will cascade Student#courses and replace the CourseStudent instance within, + // but the original CourseStudent is still contained in Student#courses that will be cascaded on flush, + // which is when the NonUniqueObjectException is thrown, because at that point, + // two CourseStudent objects with the same primary key exist. + // This can be worked around by replacing the original CourseStudent with the merged on as hinted below, + // but I'm not sure if copying the CourseStudent instance on merge really makes sense, + // since the load for the merge showed that there is no row for that key in the database. + // I tried avoiding the copy in org.hibernate.event.internal.DefaultMergeEventListener#copyEntity + // which also required updating the child-parent state in StatefulPersistenceContext to point to + // the new parent according to the MergeContext. This mostly worked, but required further investigation + // to fix a few failing tests. This copy on merge topic needs to be discussed further before continuing. + +// course.getStudents().remove( courseStudent ); +// course.getStudents().add( student.getCourses().iterator().next() ); + } ); + } + @Override protected Class[] getAnnotatedClasses() { return new Class[] { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Student.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Student.java index 7fdb90d27946..021819def8a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Student.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/idmanytoone/Student.java @@ -5,7 +5,10 @@ package org.hibernate.orm.test.annotations.idmanytoone; import java.io.Serializable; +import java.util.HashSet; import java.util.Set; + +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -25,8 +28,8 @@ public class Student implements Serializable { private String name; - @OneToMany(mappedBy = "student") - private Set courses; + @OneToMany(mappedBy = "student", cascade = CascadeType.ALL) + private Set courses = new HashSet<>(); public Student() { }