Hibernate 6 does not cascade REMOVE operation with @ManyToMany(cascade = CascadeType.ALL)
most of the time (due to BasicCollectionPersister.isCascadeDeleteEnabled
always returning false
) but seems to do so on that same collection mapped with @ManyToMany
if cascading is started with an entity containing @OneToMany
with orphanRemoval = true
. Is this intended behavior?
I have distilled this into JUnit (Jupiter) test (see below). It sets up a “school” containing students (OneToMany) and students related (many-to-many) to teachers. The test shows that deleting teachers from a student does not affect the teachers but deleting students from a school (OneToMany) seems to actually delete teachers.
class CascadeDeleteOnManyToManyTest {
private static EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
private EntityTransaction transaction;
@BeforeAll
static void setUpBeforeClass() throws Exception {
entityManagerFactory = Persistence.createEntityManagerFactory(CascadeDeleteOnManyToManyTest.class.getSimpleName());
}
@AfterAll
static void tearDown() throws Exception {
entityManagerFactory.close();
}
@BeforeEach
void setUpEntityManager() {
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
}
@AfterEach
void tearDownEntityManager() {
try {
if (transaction != null && transaction.isActive()) {
transaction.rollback();
}
} finally {
entityManager.close();
}
}
@Test
void test() {
var sc = new School();
entityManager.persist(sc);
var s1 = new Student();
var t1 = new Teacher();
s1.teachers.add(t1); t1.students.add(s1);
var t2 = new Teacher();
s1.teachers.add(t2); t2.students.add(s1);
var s2 = new Student();
s2.teachers.add(t2); t2.students.add(s2);
sc.students.add(s1); s1.school = sc;
sc.students.add(s2); s2.school = sc;
transaction.commit(); entityManager.clear(); transaction.begin();
s1 = entityManager.find(Student.class, s1.id);
s1.teachers.clear();
transaction.commit(); // does NOT: delete from TEACHER where id=?
entityManager.clear(); // aside: surprise: This makes a difference in emitted SELECTs later.
transaction.begin();
sc = entityManager.find(School.class, sc.id);
sc.students.clear(); // does: delete from TEACHER where id=?
transaction.commit();
transaction.begin();
t1 = entityManager.find(Teacher.class, t1.id);
t2 = entityManager.find(Teacher.class, t2.id);
assertNotEquals(null, t1);
assertEquals(null, t2); // why?
transaction.commit();
}
@Entity @Table(name = "SCHOOL")
public static class School {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "school")
private Set<Student> students = new HashSet<Student>();
}
@Entity @Table(name = "STUDENT")
public static class Student {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
@ManyToOne
private School school;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "STUDENT_TEACHER", //
joinColumns = @JoinColumn(name = "STUDENT_ID"), //
inverseJoinColumns = @JoinColumn(name = "TEACHER_ID"))
private Set<Teacher> teachers = new HashSet<Teacher>();
}
@Entity @Table(name = "TEACHER")
public static class Teacher {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
long id;
@ManyToMany(mappedBy = "teachers")
private Set<Student> students = new HashSet<Student>();
}
}
Just for completeness:
Here is the relevant fragment of the pom.xml:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.5.1.Final</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
Here is the persistence.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence version="3.0" xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd">
<persistence-unit name="CascadeDeleteOnManyToManyTest">
<class>playground.hibernate.CascadeDeleteOnManyToManyTest.School</class>
<properties>
<property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:test" />
<property name="jakarta.persistence.jdbc.user" value="sa" />
<property name="jakarta.persistence.jdbc.password" value="" />
<property name="jakarta.persistence.schema-generation.database.action" value="create" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>