休眠 JPA:@OneToMany删除旧的,插入新的而不刷新

2022-09-01 11:26:39

实际上,我从未完全理解冬眠中的这种行为。我在一个名为“Parent”的实体中使用@OneToMany关系,其注释如下:

@OneToMany(cascade = {CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, orphanRemoval = true)
@JoinColumn(name = "entity_id", insertable = true, updatable = true, nullable = false)
private List<Child> children;

现在我想在一个事务中执行以下操作:

  • 获取父实体
  • 循环访问子级列表
  • 删除其中一个子项
  • 插入新子项

所以,基本上我只是完全取代了其中一个孩子。

就我对这个问题的理解而言,我应该能够做这样的事情:(请注意,这只是一些java伪代码来说明问题)

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteAndAdd(Long parentId, Long childId) {
  Parent parent = entityManager.find(parentId);
  for (Iterator it = parent.children.iterator(); it.hasNext();) {
    Child child = it.next();
    if (child.id == childId) {
      it.remove();
    }
  }
  Child newChild = new Child();
  parent.children.add(newChild);
}

但是,如果新子项具有与旧子项相同的唯一键值,则此操作将失败。因此,基本上,在新子实体持久化之前,旧的子实体似乎没有被正确删除。

如果我在删除旧子项和保留新子项之间添加一个实体Manager.flush(),如下所示:

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteAndAdd(Long parentId, Long childId) {
  Parent parent = entityManager.find(parentId);
  for (Iterator it = parent.children.iterator(); it.hasNext();) {
    Child child = it.next();
    if (child.id == childId) {
      it.remove();
    }
  }
  entityManager.flush();
  Child newChild = new Child();
  parent.children.add(newChild);
}

一切都很好。在插入新子项之前,将删除子项,这是应该的。

由于我不想假设休眠混淆了发送到DB的语句的顺序,因此我假设的关于休眠的一定还有其他事情,但事实并非如此。任何想法,为什么后一个例子有效,而第一个没有?

休眠版本是3.5。DB is Mysql InnoDB


答案 1

Hibernate不知道也不尊重所有数据库约束(例如MySQL唯一约束)。这是一个他们不打算很快解决的已知问题。

休眠对刷新期间操作发生的方式具有定义的顺序

实体删除将始终在插入后发生。我所知道的唯一答案是删除约束或添加额外的刷新。

编辑:顺便说一句,定义顺序的原因是,这是保证外键约束(他们关心的约束之一)不被违反的唯一方法,即使用户做了一些不合时宜的事情。


答案 2

为了将来的读者,解决此问题的一种方法是使用延迟约束。PostgreSQL和Oracle支持它们,也许其他RDBMS也支持它们。Hibernate 将发出事务中的所有语句,而延迟将确保仅在事务提交时强制实施约束。例如,在PostgreSQL中:

ALTER TABLE company
    ADD CONSTRAINT name_unique UNIQUE (name) DEFERRABLE INITIALLY DEFERRED;

它并不理想,但它简单而有效。


推荐