Hibernate抱怨删除空集合和未触及集合的孤立对象

eqzww0vc  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(125)

更新:同时,我在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,但我不敢相信,这个休眠功能刚刚被打破,手动删除孤儿需要付出巨大的努力……所以这不是一个真正的选择。

j7dteeu8

j7dteeu81#

好了,我找到了“解决方案”。
Hibernate显然有了一些改进,并且越来越符合JPA。因此,不建议使用save(),建议使用persist()。但这只是事实的一半,因为persist()需要flush(),而save()总是保证可以获得生成的ID,例如,这就是为什么在上面的代码中看不到flush()的原因。
但这不是重点。
关键是,测试用例中的服务方法在四个用例中的两个中使用了HQL查询来检索实体e。有人会认为这是同一个物体,事实的确如此!显然,这一切都与孤儿移除或收集无关,Hibernate抱怨道。但是,在执行该查询之后,Hibernate似乎确实具有有效和一致的状态。
因此,我假设检索e的查询确实以某种方式污染了持久化上下文,并且关于之前通过save()保存的e存在一些混乱的状态。
save()之后的flush()“修复”了这个问题,尽管我不知道它为什么、如何以及为什么在没有flush()的以前的Hibernate版本中工作……
因此,可能,只是可能,这是休眠中的一个错误。但也许只是因为一些语义规则变得比过去更严格了一些,这是现在想要的行为,只是在以前的版本中意外地起到了作用。谁知道呢。

相关问题