【问题标题】:Hibernate JPA: @OneToMany delete old, insert new without flushHibernate JPA:@OneToMany 删除旧的,插入新的而不刷新
【发布时间】:2013-06-28 22:50:35
【问题描述】:

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

【问题讨论】:

  • 但是,如果新 Child 与旧 Child 具有相同的唯一键值,则此操作将失败。您是说 child 的主键与已经退出的主键相同吗?跨度>
  • 其实没有。例如,Child 上有一个复合唯一键,它由“entity_id”FK 和实体的另一个值(例如“名称”)组成。如果我删除名为“child1”的孩子并添加一个具有相同名称“child1”的新孩子,我正在观察所描述的行为。两个孩子的 FK 显然是相同的,以及我设置为相同值的名称。但是,如果在持久化新的之前将旧的删除,这应该可以正常工作。
  • 与其删除孩子,不如用新的数据更新它,如果两者具有相同的主键?
  • 在我看来冲洗模式不是自动的,您可以将冲洗模式设置为自动吗?另外,当您说它不起作用时,发生了什么?有什么例外吗?
  • @spaniard 有效点。但不幸的是,这并不能解释我遇到的行为。而且 PK 不一定相同,但我同意在大多数情况下仍然可以更新而不是在需要时删除和插入新对象。但是,如果我们进一步讨论,问题通常从接口规范开始。

标签: java hibernate jpa


【解决方案1】:

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

Hibernate 有一个 defined order 用于在刷新期间发生操作的方式。

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

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

【讨论】:

  • 我会假设,我正在使用“集合元素”?至少 Child 在 Parent 的集合中,这是我调用 entityManager 的地方。在这种情况下,文档指定在插入之前删除。这让我有些困惑。
  • 我相信“集合”元素是指多对多关联的连接表。
  • 我终于找到了一些东西:docs.jboss.org/hibernate/orm/3.3/reference/en/html/… "集合几乎可以包含任何其他 Hibernate 类型,包括:基本类型、自定义类型、组件和对其他实体的引用。这是一个重要的区别。一个对象在一个集合可以用“值”语义来处理(它的生命周期完全取决于集合所有者),或者它可能是对另一个具有自己生命周期的实体的引用。在后一种情况下,只有两者之间的“链接” objects 被认为是集合持有的状态。”
  • 所以,在这种情况下,“集合元素”可能只管理实体的外键字段。在那种情况下,这实际上是有道理的。
【解决方案2】:

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

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

并不理想,但简单有效。

【讨论】:

  • 为什么不理想?能详细点吗?
  • @Michael 一方面,因为它取决于并非所有数据库服务器都支持的特定功能。这应该在 Hibernate 中得到普遍解决。
猜你喜欢
  • 1970-01-01
  • 2014-10-17
  • 2014-11-16
  • 1970-01-01
  • 2011-03-19
  • 2013-03-03
  • 1970-01-01
  • 2011-07-03
  • 1970-01-01
相关资源
最近更新 更多