How to add persisted entities in a relationship ma

2019-07-26 04:59发布

问题:

I have an Entity with a bidirectional ManyToMany relationship (on the same entity) marked as CascadeType.ALL.

Here is how it looks for the Contact entity:

@ManyToMany(cascade= CascadeType.ALL)
private List<Contact> parentContacts = new ArrayList<Contact>();
@ManyToMany(cascade= CascadeType.ALL, mappedBy="parentContacts")
private List<Contact> childContacts = new ArrayList<Contact>();

I have a method on the server side supposed to do save this entity:

public AltContact saveContact(EntityManager em, Contact contact, List<Contact> childs) {
    for (Contact c : childs) {
        contact.getChildContacts().add(c);
        c.getParentContacts().add(contact);
    }
    if (contact.getId() == null) {
        em.persist(contact);
    } else {
        contact = em.merge(contact);
    }
    return contact;
}

It works great if the contact or any entities of the childs list are not persisted. But sometimes i will need to use the saveContact method with already persisted entities, and when i try to do this, it seems like JPA try to persist all the entities of the relationship and then will raise the exception:

Internal Exception: java.sql.SQLIntegrityConstraintViolationException

Indeed, it'll try to repersist and already persisted entity, so the unicity of the ID field of Contact will be violated.

How can active this? What's wrong with my approach?

How can i make Eclipselink/JPA does not try to persist entities that are part of the relationship and that are already persisted?

回答1:

If your parent contact Entity is new but some of the children exist (but are detached), you do not want to call persist on the parent - this will cause the persist to cascade to the children which is required to cause an exception to be thrown either immediately or on the flush/commit. If you wish to call perist on the new parent contact, then you should try to find the existing children and associate the managed instances to the new contact. If it doesn't exist, you can persist the child individually or allow persist to cascade over the relationship to do it for you.

Generally though, it is much easier just to call merge on the new parent contact and have the provider figure out if it should be an update or an insert. Since merge cascades to the children, they too will be inserted or updated as appropriate.

Be careful with the use of cascading though. When you go to remove a parent, it will also cascade the remove to the children, which then cascades to every parent in their parent collections. If it is not managed correctly, it could remove all contacts unintentionally. Also note that changes that cause a child to be removed from the list might not cascade correctly - cascade merge can only operate on entities that are currently in the collection, not ones that used to be there. So you may need to manually call merge on children when removing them from the parent's collection or ensure they have merged called through a different path.