退出事务上下文后,Hibernate 过多删除了一个子实体

发布于 2025-01-10 05:49:25 字数 3138 浏览 0 评论 0原文

我在一个简单的 Spring Boot 应用程序中遇到了一个奇怪的问题,我在其中更新父实体,删除其所有子实体,然后创建新实体。

保存后,生成的实体看起来很好并且具有所有新的子实体,但是当我再次查询时,我发现一个子实体丢失了!

对此的快速而肮脏的解决方案是将代码拆分为两个事务,但我想了解孤儿删除这样做的原因。

这是服务代码:

@Service
public class ParentService {
    private final EntityManager em;

    public ParentEntity getParent(UUID parentId) {
        return em.createQuery(
                        "SELECT p " +
                                "from ParentEntity p " +
                                "JOIN FETCH p.children " +
                                "WHERE p.id = :parentId", ParentEntity.class)
                .setParameter("parentId", parentId)
                .getSingleResult();
    }

    @Transactional
    public ParentEntity resetChildren(UUID parentId) {
        var parent = getParent(parentId);

        parent.getChildren().clear();
        addChildren(parent, 2);

        em.persist(parent);
        return parent;
    }

    private void addChildren(ParentEntity parent, int childCount) {
        for (var i = 0; i < childCount; i++) {
            parent.addChildren(new ChildEntity());
        }
    }
}

重置子级并再次获取父级后的 SQL 输出是这样的:

select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?
insert into child (parent_id, id) values (?, ?)
insert into child (parent_id, id) values (?, ?)
delete from child where id=?
delete from child where id=?
delete from child where id=? <-- One extra delete

select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?

实体看起来像这样:

父级

@Entity
@Table(name = "parent")
public class ParentEntity {
    @Id
    @GeneratedValue
    private UUID id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<ChildEntity> children = new HashSet<>();
    ...

    public void addChildren(ChildEntity child) {
        this.children.add(child);
        child.setParent(this);
    }
    ...
}

和子级

@Entity
@Table(name = "child")
public class ChildEntity {

    @Id
    @GeneratedValue
    private UUID id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "parent_id")
    private ParentEntity parent;
    ...
}

对此的一个重要说明是,在我的用例中,这些实体的 id 是UUID。我无法让它与任何数字 ID 一起使用

带有单元测试的代码存储库可以在此处找到

一件有趣的事情发生了,如果我决定添加 1 个子级,而不是两个或更多,则父级本身将被删除!因此,感觉就像我正在查看 Hibernate 中的一个错误。

I've encountered a strange problem in a simple Spring Boot application, where I update a parent entity, delete all of its children and then create new ones.

After saving, the resulting entity looks fine and has all of the new children, but when I query it again, I find that one of the child entities is lost!

The quick and dirty solution for this would be to split the code into two transactions, but I want to understand the cause the orphan removal to act like this.

Here's the service code:

@Service
public class ParentService {
    private final EntityManager em;

    public ParentEntity getParent(UUID parentId) {
        return em.createQuery(
                        "SELECT p " +
                                "from ParentEntity p " +
                                "JOIN FETCH p.children " +
                                "WHERE p.id = :parentId", ParentEntity.class)
                .setParameter("parentId", parentId)
                .getSingleResult();
    }

    @Transactional
    public ParentEntity resetChildren(UUID parentId) {
        var parent = getParent(parentId);

        parent.getChildren().clear();
        addChildren(parent, 2);

        em.persist(parent);
        return parent;
    }

    private void addChildren(ParentEntity parent, int childCount) {
        for (var i = 0; i < childCount; i++) {
            parent.addChildren(new ChildEntity());
        }
    }
}

The SQL output after resetting children and the fetching the parent again is this:

select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?
insert into child (parent_id, id) values (?, ?)
insert into child (parent_id, id) values (?, ?)
delete from child where id=?
delete from child where id=?
delete from child where id=? <-- One extra delete

select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?

The entities look like this:

The parent

@Entity
@Table(name = "parent")
public class ParentEntity {
    @Id
    @GeneratedValue
    private UUID id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<ChildEntity> children = new HashSet<>();
    ...

    public void addChildren(ChildEntity child) {
        this.children.add(child);
        child.setParent(this);
    }
    ...
}

And the child

@Entity
@Table(name = "child")
public class ChildEntity {

    @Id
    @GeneratedValue
    private UUID id;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "parent_id")
    private ParentEntity parent;
    ...
}

An important note to this, that in my use case, the id of these entities is a UUID. I can't get it working with any numeric ids

The code repository with a unit test can be found here

An interesting thing happens, if I decide to add 1 child, instead of two or more, the parent itself is deleted! Because of this, it feels like I'm looking at a bug in Hibernate.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

攒一口袋星星 2025-01-17 05:49:25

问题是我在子端有级联,这导致了非常非常奇怪的结果。我不希望父级在级联上被删除,所以我不需要它。

解决方法是将孩子更改为:

@Entity
@Table(name = "child")
public class ChildEntity {

    @Id
    @GeneratedValue
    private UUID id;

    @ManyToOne // <---- No more cascade!
    @JoinColumn(name = "parent_id")
    private ParentEntity parent;
    ...
}

The problem was that I had cascade on the child side, which lead to very very weird results. I don't want parent to be deleted on cascade, so I don't need that.

The fix was to change the child to this:

@Entity
@Table(name = "child")
public class ChildEntity {

    @Id
    @GeneratedValue
    private UUID id;

    @ManyToOne // <---- No more cascade!
    @JoinColumn(name = "parent_id")
    private ParentEntity parent;
    ...
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文