简体   繁体   English

如何避免与 JPA 级联重复?

[英]How to avoid duplicates with JPA cascades?

I have a Parent entity with a Child entity in a ManyToOne relationship:我有一个多对一关系子实体实体:

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

The Child has an unique field: Child有一个独特的字段:

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

When I need a new Child , I ask the ChildDAO first:当我需要一个新的Child 时,我首先询问ChildDAO

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

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

The problem is, if I do like above twice (with the same name for the Child ), and only persist the Parent at the end, I get a constraint exception.问题是,如果我喜欢上面两次(与Child同名),并且最后只保留Parent ,我会得到一个约束异常。 Which seems normal, since initially there was no child in the database with the specified name.这似乎很正常,因为最初数据库中没有指定名称的子项。

The problem is, I'm not sure what would be the best way to avoid this situation.问题是,我不确定避免这种情况的最佳方法是什么。

You are creating two non-persistent instances of Child with new Child() twice then putting these in two different Parents.您正在使用new Child()创建两个 Child 的非持久性实例,然后将它们放入两个不同的 parent。 When you persist the Parent objects, each of the two new Child instances will be persisted/inserted via cascade, each with a different @Id .当您持久化 Parent 对象时,两个新的 Child 实例中的每一个都将通过级联持久化/插入,每个实例都有不同的@Id The unique constraint on the name then breaks.名称上的唯一约束随后中断。 If you're doing CascadeType.ALL, then every time you do new Child() you may be getting a separate persistent object.如果您正在执行 CascadeType.ALL,那么每次执行new Child()您可能会得到一个单独的持久对象。

If you really wanted the two Child instances to be treated as a single persistent object with the same ID, you would need to persist it separately to associate with the persistence context/session.如果您真的希望将两个 Child 实例视为具有相同 ID 的单个持久对象,则需要单独持久化它以与持久性上下文/会话相关联。 Subsequent calls to childDao.findByName would then flush the insert and return the new Child you just created, so you won't be doing new Child() twice.随后对childDao.findByName调用将刷新插入并返回您刚刚创建的新 Child,因此您不会执行new Child()两次。

You're getting this because you're attempting to persist an object that already exists (same ID).你得到这个是因为你试图持久化一个已经存在的对象(相同的 ID)。 Your cascade probably isn't persist it is MERGE/UPDATE/REMOVE/DETACH.您的级联可能不是持久的,它是 MERGE/UPDATE/REMOVE/DETACH。 The best way to avoid this situation is set up the cascade properly or manage the cascades manually.避免这种情况的最佳方法是正确设置级联或手动管理级联。 Basically speaking, cascading persists is the usual culprit.基本上来说,级联持续是通常的罪魁祸首。

You probable want something like this:你可能想要这样的东西:

//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.
}

The cascades can be extremely frustrating if you let the framework manage it because you'll occasionally miss updates if it is caching (particularly with Collections in ManyToOne relationships).如果让框架管理级联,级联可能会非常令人沮丧,因为如果它正在缓存(特别是多对一关系中的集合),您偶尔会错过更新。 If you don't explicitly save the parent and allow the framework to deal with the cascades.如果您没有明确保存父级并允许框架处理级联。 Generally speaking I allow a Cascade.ALL from my parent in a relationship and a cascade of all by DELETE/PERSIST from my children.一般来说,我允许在一段关系中来自我父母的 Cascade.ALL 和来自我孩子的 DELETE/PERSIST 的所有级联。 I'm an Eclipselink user so my experience with Hibernate/other is limited and I can't speak to the caching.我是 Eclipselink 用户,所以我在 Hibernate/other 方面的经验有限,我无法谈论缓存。 * Really, think about it--if you're saving a child do you really want to save all the objects related to it? * 真的,想想看——如果你在拯救一个孩子,你真的想拯救所有与它相关的物体吗? Also, if you're adding a child, shouldn't you notify the parent?另外,如果你要添加一个孩子,你不应该通知父母吗? * *

In your case I would simply have a "saveParent" and "saveChild" method in my Dao/Service that made sure the cache is correct and the database is correct to avoid the headache.在您的情况下,我只会在我的 Dao/Service 中使用“saveParent”和“saveChild”方法,以确保缓存正确且数据库正确以避免头痛。 Managing manually means you'll have absolute control and you won't have to rely on the cascades to do your dirty work.手动管理意味着您将拥有绝对的控制权,而不必依赖级联来完成繁重的工作。 In a single-direction they are fantastic, the minute they come "upstream" you're going to have some unexpected behaviors.在一个方向上,它们很棒,一旦它们来到“上游”,你就会有一些意想不到的行为。

If you make two objects, and let JPA persist them automatically, then you'll get two rows in the database.如果您创建两个对象,并让 JPA 自动持久化它们,那么您将在数据库中获得两行。 So, you have two options: don't make two objects, or don't let JPA persist them automatically.因此,您有两个选择:不要创建两个对象,或者不要让 JPA 自动持久化它们。

To avoid making two objects, you would have to arrange your code so if two Parents try to create Children with the same name, they will get the same actual instance.为了避免创建两个对象,您必须安排您的代码,以便如果两个父项尝试创建具有相同名称的子项,他们将获得相同的实际实例。 You would probably want a WeakHashMap (scoped to the current request, perhaps by being referred to by local variables), keyed by name, where Parents can look up their new Child's name to see if the Child already exists.您可能需要一个WeakHashMap (范围为当前请求,可能通过局部变量引用),以名称为键,父母可以在其中查找新孩子的名称以查看孩子是否已经存在。 If not, they can create the object and put it in the map.如果没有,他们可以创建对象并将其放入地图中。

To avoid having JPA persist the objects automatically, drop the cascade and use persist to manually add the objects to the context immediately after creation.为避免 JPA 自动持久化对象,请删除级联并在创建后立即使用persist手动将对象添加到上下文中。

Since a persistence context is basically a tricked-out WeakHashMap attached to a database, these approaches are pretty similar when it comes down to it.由于持久化上下文基本上是一个附加到数据库的经过精心设计的 WeakHashMap,因此归根结底,这些方法非常相似。

You are setting a child object then if persist the parent you are storing a register in the database that points to a non existent child.您正在设置一个子对象,然后如果坚持父对象,您将在数据库中存储一个指向不存在的子对象的寄存器。

When you create a new object it must be managed by the entityManager in the same way when you "find" an object using the DAO it must get the register from DB and put the object in the entityManager context.当您创建一个新对象时,它必须由 entityManager 以相同的方式管理,当您使用 DAO“查找”一个对象时,它必须从 DB 获取寄存器并将该对象放入 entityManager 上下文中。

Try to first persist or merge the child object.尝试先持久化或合并子对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM