orphanRemoval 不适用于通过 SpringData .save 方法保留了 naturalId 的实体
orphanRemoval does not work for entities with naturalId persisted via SpringData .save method
UPDATE/TLDR:合并到持久性上下文中的实体无法正确处理带有 orphanRemoval 注释的集合。
最简单的例子在这里:https://github.com/alfonz19/orphan-removal-test/tree/justMergeFlow
详情请见README.md。
原创 POST:
Spring有这个保存方法
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
这带来了一些方便的行为,但它似乎会导致令人惊讶的事情,因为如果您最初通过持久、合并或保存创建实体,那么孤儿删除似乎会受到影响。如果您可以对此发表评论,请发表评论。我还没有最小的例子,但理论上我可以创建它。
讨论的场景:
- 你从空数据库开始,你有微不足道的 1:N 关联。它只是将一些字符串映射到实体,就像您想避免使用逗号分隔的列表一样。
关联的非拥有方(1:
),具有 naturalID,关联注释为:
@OneToMany(mappedBy = "xxx", cascade = CascadeType.ALL, orphanRemoval = true)
关联的拥有方注释如下,实体具有复合自然pk:
@EmbeddedId
private PK pk;
@ManyToOne
@JoinColumn(name = "xxx", insertable = false, updatable = false)
@Setter(AccessLevel.NONE)
private XXX xxx;
- 处理中。单个事务,您在新状态下创建实体,将一个关联实体添加到列表中,然后 保存 实体(稍后我们将返回 保存 操作) .现在我们从集合中删除该项目(removeIf 给定项目/清除/其他)和另一个。 TX 提交。
现在数据库中会有什么?嗯,我有 3 个不同的结果:
一个。如果最初的 save 操作是 entityManager.persist,结果如我所料:顶级记录存在并且有单个关联项,我们没有删除的那个它的关联列表。太棒了
b。如果最初的 save 操作是 entityManager.merge 或 SimpleJpaRepository#save (这将调用合并,因为 isNew 行为是 naturalId 实体;id != null --> 据称它是分离的) ,实体也将被创建,但列表上的修改不会被保留,这意味着即使我可以看到,在提交之前实体的目标状态是变体 a) 中描述的状态,我们从关联中删除的项目将坚持而另一个不坚持。 IE。我们调用 save 的状态将被插入,关联列表的进一步更改将不会反映出来。
c。如果没有离奇的案例,每一个这样的问题都不会完整,我很高兴我不必让你失望。当所有这些之间有更多操作,但没有触及这些实体时,可能会引发一些刷新,最后我会在关联集合中留下两个实体;这意味着顶级实体必须已被管理并反映了对其关联实体的更改,但不知何故休眠不需要感受到删除实体的压力,该实体已从该集合中删除。
解决方案(或者可能是变通方法)很简单:如果您知道自己在坚持,就使用坚持。简单。然后它将正常工作而不会打嗝。而且我认为这是正确的举动,我个人不喜欢 SimpleJpaRepository#save 背后的想法。但我觉得我可能遗漏了一些东西,因为编写 save 方法似乎很危险,如果这是可能的结果,它将坚持或合并。而且我绝对看不出场景 b/c 的原因。即使我在这里不正确地使用合并,实体在被带到持久化上下文之后被管理,并且应该正确处理接触它的关联集合,但事实并非如此。
备注:
- 是的,一切都在单个 tx 中完成。
- 我检查了每个实体是否以及何时处于持久性上下文中,我没有发现用作 save 方法场景的 persist/merge 之间有任何区别。所以我不知道为什么会有不同的结果。
知道我还能检查什么吗?甚至我的错误在哪里?
这被确定为 hibernate 中的一个错误,它已经被修复,修复应该是 6.0.0 版本的一部分。欲了解更多信息:
UPDATE/TLDR:合并到持久性上下文中的实体无法正确处理带有 orphanRemoval 注释的集合。
最简单的例子在这里:https://github.com/alfonz19/orphan-removal-test/tree/justMergeFlow
详情请见README.md。
原创 POST:
Spring有这个保存方法
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
这带来了一些方便的行为,但它似乎会导致令人惊讶的事情,因为如果您最初通过持久、合并或保存创建实体,那么孤儿删除似乎会受到影响。如果您可以对此发表评论,请发表评论。我还没有最小的例子,但理论上我可以创建它。
讨论的场景:
- 你从空数据库开始,你有微不足道的 1:N 关联。它只是将一些字符串映射到实体,就像您想避免使用逗号分隔的列表一样。
关联的非拥有方(1:
),具有 naturalID,关联注释为:
@OneToMany(mappedBy = "xxx", cascade = CascadeType.ALL, orphanRemoval = true)
关联的拥有方注释如下,实体具有复合自然pk:
@EmbeddedId
private PK pk;
@ManyToOne
@JoinColumn(name = "xxx", insertable = false, updatable = false)
@Setter(AccessLevel.NONE)
private XXX xxx;
- 处理中。单个事务,您在新状态下创建实体,将一个关联实体添加到列表中,然后 保存 实体(稍后我们将返回 保存 操作) .现在我们从集合中删除该项目(removeIf 给定项目/清除/其他)和另一个。 TX 提交。
现在数据库中会有什么?嗯,我有 3 个不同的结果:
一个。如果最初的 save 操作是 entityManager.persist,结果如我所料:顶级记录存在并且有单个关联项,我们没有删除的那个它的关联列表。太棒了
b。如果最初的 save 操作是 entityManager.merge 或 SimpleJpaRepository#save (这将调用合并,因为 isNew 行为是 naturalId 实体;id != null --> 据称它是分离的) ,实体也将被创建,但列表上的修改不会被保留,这意味着即使我可以看到,在提交之前实体的目标状态是变体 a) 中描述的状态,我们从关联中删除的项目将坚持而另一个不坚持。 IE。我们调用 save 的状态将被插入,关联列表的进一步更改将不会反映出来。
c。如果没有离奇的案例,每一个这样的问题都不会完整,我很高兴我不必让你失望。当所有这些之间有更多操作,但没有触及这些实体时,可能会引发一些刷新,最后我会在关联集合中留下两个实体;这意味着顶级实体必须已被管理并反映了对其关联实体的更改,但不知何故休眠不需要感受到删除实体的压力,该实体已从该集合中删除。
解决方案(或者可能是变通方法)很简单:如果您知道自己在坚持,就使用坚持。简单。然后它将正常工作而不会打嗝。而且我认为这是正确的举动,我个人不喜欢 SimpleJpaRepository#save 背后的想法。但我觉得我可能遗漏了一些东西,因为编写 save 方法似乎很危险,如果这是可能的结果,它将坚持或合并。而且我绝对看不出场景 b/c 的原因。即使我在这里不正确地使用合并,实体在被带到持久化上下文之后被管理,并且应该正确处理接触它的关联集合,但事实并非如此。
备注:
- 是的,一切都在单个 tx 中完成。
- 我检查了每个实体是否以及何时处于持久性上下文中,我没有发现用作 save 方法场景的 persist/merge 之间有任何区别。所以我不知道为什么会有不同的结果。
知道我还能检查什么吗?甚至我的错误在哪里?
这被确定为 hibernate 中的一个错误,它已经被修复,修复应该是 6.0.0 版本的一部分。欲了解更多信息: