Skip to content

Commit

Permalink
HHH-11026 Test cascade merge many-to-many leading to exception
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Feb 19, 2025
1 parent 76359ad commit b71a715
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,8 +28,8 @@ public class Course implements Serializable {

private String name;

@OneToMany(mappedBy = "course")
private Set<CourseStudent> students;
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL)
private Set<CourseStudent> students = new HashSet<>();

public Course() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,8 +28,8 @@ public class Student implements Serializable {

private String name;

@OneToMany(mappedBy = "student")
private Set<CourseStudent> courses;
@OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
private Set<CourseStudent> courses = new HashSet<>();

public Student() {
}
Expand Down

0 comments on commit b71a715

Please sign in to comment.