简体   繁体   中英

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:

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

When I need a new Child , I ask the ChildDAO first:

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. 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. When you persist the Parent objects, each of the two new Child instances will be persisted/inserted via cascade, each with a different @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.

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

You're getting this because you're attempting to persist an object that already exists (same ID). Your cascade probably isn't persist it is 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. I'm an Eclipselink user so my experience with Hibernate/other is limited and I can't speak to the caching. * 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. 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. So, you have two options: don't make two objects, or don't let JPA persist them automatically.

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

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.

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.

Try to first persist or merge the child object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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