退出事务上下文后,Hibernate 删除了一个 child 实体太多
Hibernate deletes one child enitity too much, after exiting Transactional context
我在一个简单的 Spring 引导应用程序中遇到了一个奇怪的问题,我在其中更新了一个 parent 实体,删除了它的所有 children,然后创建了新的。
保存后,生成的实体看起来不错,有所有新的children,但是当我再次查询时,我发现一个 child 实体丢失了!
快速而肮脏的解决方案是将代码拆分为两个事务,但我想了解孤儿删除的原因。
这是服务代码:
@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());
}
}
}
重置 children 并再次获取 parent 后的 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=?
实体如下所示:
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);
}
...
}
还有 child
@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
可以找到带有单元测试的代码库here
一件有趣的事情发生了,如果我决定添加 1 个 child,而不是两个或更多,parent 本身会被删除!正因为如此,感觉就像我在看 Hibernate 中的一个错误。
问题是我在 child 一侧有 级联,这导致了非常非常奇怪的结果。我不想 parent 在级联中被删除,所以我不需要那个。
修复方法是将 child 更改为:
@Entity
@Table(name = "child")
public class ChildEntity {
@Id
@GeneratedValue
private UUID id;
@ManyToOne // <---- No more cascade!
@JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}
我在一个简单的 Spring 引导应用程序中遇到了一个奇怪的问题,我在其中更新了一个 parent 实体,删除了它的所有 children,然后创建了新的。
保存后,生成的实体看起来不错,有所有新的children,但是当我再次查询时,我发现一个 child 实体丢失了!
快速而肮脏的解决方案是将代码拆分为两个事务,但我想了解孤儿删除的原因。
这是服务代码:
@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());
}
}
}
重置 children 并再次获取 parent 后的 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=?
实体如下所示:
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);
}
...
}
还有 child
@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
可以找到带有单元测试的代码库here
一件有趣的事情发生了,如果我决定添加 1 个 child,而不是两个或更多,parent 本身会被删除!正因为如此,感觉就像我在看 Hibernate 中的一个错误。
问题是我在 child 一侧有 级联,这导致了非常非常奇怪的结果。我不想 parent 在级联中被删除,所以我不需要那个。
修复方法是将 child 更改为:
@Entity
@Table(name = "child")
public class ChildEntity {
@Id
@GeneratedValue
private UUID id;
@ManyToOne // <---- No more cascade!
@JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}