如何避免与JPA级联重复?

时间:2011-11-27 19:50:12

标签: java jpa

我的实体在 ManyToOne 关系中有 Child 实体:

@Entity class Parent {
  // ...
  @ManyToOne((cascade = {CascadeType.ALL})
  private Child child;
  // ...
}

儿童有一个唯一字段:

@Entity class Child {
  // ...
  @Column(unique = true)
  private String name;
  // ...
}

当我需要一个新的孩子时,我先问 ChildDAO

Child child = childDao.findByName(name);
if(child == null) {
  child = new Child(name);
}

Parent parent = new Parent();
parent.setChild(child);

问题是,如果我喜欢两次孩子的名称相同),并且只保留最后,我得到一个约束异常。这似乎很正常,因为最初在数据库中没有具有指定名称的子项。

问题是,我不确定避免这种情况的最佳方法是什么。

4 个答案:

答案 0 :(得分:9)

您正在创建两个具有new Child()的Child的非持久性实例,然后将它们放在两个不同的Parent中。当您持久保存Parent对象时,两个新的Child实例中的每一个都将通过级联持久化/插入,每个实例具有不同的@Id。然后,对名称的唯一约束会中断。如果您正在使用CascadeType.ALL,那么每次执行new Child()时,您可能会获得一个单独的持久对象。

如果您确实希望将两个Child实例视为具有相同ID的单个持久对象,则需要将其单独保留以与持久性上下文/会话关联。对childDao.findByName的后续调用将刷新插入并返回刚创建的新Child,因此您不会执行new Child()两次。

答案 1 :(得分:6)

你得到这个是因为你试图坚持一个已经存在的对象(相同的ID)。你的级联可能不会持续它是MERGE / UPDATE / REMOVE / DETACH。避免这种情况的最佳方法是正确设置级联或手动管理级联。基本上说,级联仍然是通常的罪魁祸首。

你可能想要这样的东西:

//SomeService--I assume you create ID's on persist
saveChild(Parent parent, Child child)
{
  //Adding manually (using your cascade ALL)
  if(child.getId() == null) //I don't exist persist
    persistence.persist(child);
  else
    persistence.merge(child); //I'm already in the DB, don't recreate me

  parent.setChild(child);
  saveParent(parent); //using a similar "don't duplicate me" approach.
}

如果让框架管理它,级联可能会非常令人沮丧,因为如果它是缓存的话,你偶尔会错过更新(特别是在ManyToOne关系中的Collections)。如果您没有明确保存父级并允许框架处理级联。一般来说,我允许来自我父母的Cascade.ALL和我孩子们的DELETE / PERSIST级联。我是一个Eclipselink用户,所以我对Hibernate / other的体验有限,我无法谈论缓存。 *真的,想一想 - 如果你正在拯救一个孩子,你真的想保存所有与之相关的对象吗?另外,如果你要加孩子,你不应该通知家长吗? *

在你的情况下,我只需要在我的Dao / Service中使用“saveParent”和“saveChild”方法,以确保缓存正确并且数据库正确以避免头痛。手动管理意味着您将拥有绝对的控制权,您不必依赖级联来完成您的脏工作。在单向方向上它们非常棒,它们“上游”的那一刻你将会有一些意想不到的行为。

答案 2 :(得分:4)

如果您创建了两个对象,并让JPA自动保留它们,那么您将在数据库中获得两行。因此,您有两种选择:不要制作两个对象,或者不要让JPA自动保留它们。

为了避免制作两个对象,您必须安排代码,这样如果两个Parent尝试创建具有相同名称的Children,它们将获得相同的实际实例。你可能想要一个WeakHashMap(作用于当前请求的范围,可能是由本地变量引用),按名称键入,其中父母可以查找他们的新Child名称以查看Child是否已经存在。如果没有,他们可以创建对象并将其放在地图中。

为避免让JPA自动保留对象,请删除级联并使用persist在创建后立即手动将对象添加到上下文中。

由于持久化上下文基本上是附加到数据库的欺骗性WeakHashMap,因此这些方法在归结为它时非常相似。

答案 3 :(得分:1)

您正在设置子对象,如果您将要存储寄存器的父项存储在指向不存在的子项的数据库中。

当你创建一个新对象时,它必须由entityManager以与使用DAO“找到”对象时相同的方式管理它必须从DB获取寄存器并将对象放在entityManager上下文中。

尝试先保留或合并子对象。