更新:同时,我在Hibernate追踪器中打开了一个问题HHH-15512。
我最近被迫将Hibernate从5.x升级到6.x,我使用的是最新版本6.1.2 Final。
我有一个参数化的测试用例,它被示例化了四次,并对一个实体做了一些事情。仅在四个示例中的*两个示例中,提交事务会导致以下错误:A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance
实体的相关部分如下:
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<>();
}
当然,它还有许多其他属性和集合,但这里的相关部分是两个集合。
测试用例修改了其中的一个--childOnes
。Hibernate异常抱怨Other集合-childTwos
。但在整个测试用例中从未涉及到该集合。
测试用例如下(简化):
@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 ...
}
我发现将实体重新加载到单独的testee
似乎是多余的,但当我删除它并使用原始的e
时也会出现错误。
我已经尝试调试该问题,并在Hibernate的Collections.Java中发现了以下Hibernate代码:
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()
);
}
调试器告诉我e.getStatus() == Status.MANAGED
,这显然是异常的根本原因。
但为什么Hibernate甚至要尝试对一个完全未接触且从未使用过的集合执行orpahn移除呢?为什么Hibernate要在一个显然是仍然托管的实体的子级的集合上执行孤立删除?
更有趣的是:为什么Hibernate只在四个测试用例示例中的两个示例中出现错误?它们都做了同样的事情(至少在Hibernate抱怨的集合方面是这样)。
我们可以做些什么来解决这个问题呢?我目前正在考虑删除所有地方的orphanRemoval=true
,但我不敢相信,这个休眠功能刚刚被打破,手动删除孤儿需要付出巨大的努力……所以这不是一个真正的选择。
1条答案
按热度按时间j7dteeu81#
好了,我找到了“解决方案”。
Hibernate显然有了一些改进,并且越来越符合JPA。因此,不建议使用
save()
,建议使用persist()
。但这只是事实的一半,因为persist()
需要flush()
,而save()
总是保证可以获得生成的ID,例如,这就是为什么在上面的代码中看不到flush()
的原因。但这不是重点。
关键是,测试用例中的服务方法在四个用例中的两个中使用了HQL查询来检索实体
e
。有人会认为这是同一个物体,事实的确如此!显然,这一切都与孤儿移除或收集无关,Hibernate抱怨道。但是,在执行该查询之后,Hibernate似乎确实具有有效和一致的状态。因此,我假设检索
e
的查询确实以某种方式污染了持久化上下文,并且关于之前通过save()
保存的e
存在一些混乱的状态。save()
之后的flush()
“修复”了这个问题,尽管我不知道它为什么、如何以及为什么在没有flush()
的以前的Hibernate版本中工作……因此,可能,只是可能,这是休眠中的一个错误。但也许只是因为一些语义规则变得比过去更严格了一些,这是现在想要的行为,只是在以前的版本中意外地起到了作用。谁知道呢。