28

我实际上从来没有完全理解休眠中的这种行为。我在名为“父”的实体中使用@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);
}

但是,如果新 Child 与旧 Child 具有相同的唯一键值,则此操作将失败。所以,基本上看起来旧的子实体在新实体被保留之前没有被正确删除。

如果我在删除旧孩子和保留新孩子之间添加一个 entityManager.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);
}

一切正常。孩子在插入新的之前被删除,这是应该的。

由于我不想假设 hibernate 混淆了发送到数据库的语句的顺序,所以我对 hibernate 的假设肯定不是这样。任何想法为什么后一个示例有效,而第一个示例无效?

休眠版本是 3.5。DB是Mysql InnoDB

4

2 回答 2

37

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

Hibernate为刷新期间操作发生的方式定义了顺序。

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

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

于 2013-07-01T19:15:00.923 回答
28

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

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

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

于 2014-03-11T20:00:21.387 回答