Update: In the meantime, I opened an issue HHH-15512 in the hibernate tracker.
I was recently forced to upgrade Hibernate from 5.x to 6.x and I am using the currently most recent version 6.1.2.Final.
Among the things that broke apart is that I have a parameterized test case that gets instantiated four times and doing something with a Entity. In only two of the four instances, committing the transaction results in the following error:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance
The relevant part of the entity is this:
public class MyEntity {
@LazyCollection(LazyCollectionOption.TRUE)
@Cascade(CascadeType.ALL)
@OnDelete(action = OnDeleteAction.CASCADE)
@OneToMany(orphanRemoval = true, mappedBy = "parent")
private final List<ChildOne> childOnes = new ArrayList<>();
@LazyCollection(LazyCollectionOption.TRUE)
@Cascade(CascadeType.ALL)
@OnDelete(action = OnDeleteAction.CASCADE)
@OneToMany(orphanRemoval = true, mappedBy = "parent")
private final List<ChildTwo> childTwos = new ArrayList<>();
}
Of course it has many other attributes and collections, but the relevant part are two collections here.
The test case modifies one of them - childOnes. The hibernate exception complains about the other collection - childTwos. But that collection is never touched in the entire test case.
The test case goes like this (simplified):
@BeforeEach
void openDatabaseSession() {
dbTx = sfp.beginTransaction();
}
@AfterEach
void closeDatabaseSession() {
dbTx.commit();
}
@ParameterizedTest
@MethodSource("irrelevantSource")
void doTest(/* some irrelevant params */) {
final var child = new ChildOne();
// given
final var e = new MyEntity();
e.addChildOne(child);
sfp.getCurrentSession().save(e);
// when
final var result = service.someMethod(e); // not altering childTwos!
// then
final var testee = sfp.getCurrentSession().find(MyEntity.class, e.getId());
assertNotNull(testee);
var c = testee.getChildOnes();
// ... some assertions ...
}
I see that reloading the entity into a separate testee seems somehow redundant, but the error also occurs when I remove that and work with the original e.
I have already tried to debug the problem and found the following Hibernate code in Hibernate's Collections.java:
final EntityEntry e = persistenceContext.getEntry( owner );
//only collections belonging to deleted entities are allowed to be dereferenced in the case of orphan delete
if ( e != null && e.getStatus() != Status.DELETED && e.getStatus() != Status.GONE ) {
throw new HibernateException(
"A collection with cascade=\"all-delete-orphan\" was no longer referenced by the owning entity instance: " +
loadedPersister.getRole()
);
}
The debugger tells me that e.getStatus() == Status.MANAGED, which is obviously the root cause of the Exception.
But why is Hibernate even trying to perform orpahn removal on a completely untouched and never used collection? And why does Hibernate perform orphan removal on a collection that is apparently the child of a still managed entity?
And more interestingly: why does Hibernate only complain in two out of the four test case instances? They all do the same (at least regarding the collection Hibernate complains about).
And what can be done to fix that? I am currently thinking about just removing orphanRemoval=true everywhere but I resist to believe, that this Hibernate feature is just broken and it would take huge effort to delete the orphans manually... So it's not a real option.