如何避免与JPA级联重复?

Cos*_*s64 9 java jpa

我有一个Parent实体与ManyToOne关系中的Child实体:

@Entity class Parent {
  // ...
  @ManyToOne((cascade = {CascadeType.ALL})
  private Child child;
  // ...
}
Run Code Online (Sandbox Code Playgroud)

儿童有一个独特的领域:

@Entity class Child {
  // ...
  @Column(unique = true)
  private String name;
  // ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

Parent parent = new Parent();
parent.setChild(child);
Run Code Online (Sandbox Code Playgroud)

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

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

wrs*_*der 10

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

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


Dan*_*man 8

你得到这个是因为你试图坚持一个已经存在的对象(相同的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.
}
Run Code Online (Sandbox Code Playgroud)

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

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