JPA - Removing of elements of a bidirectional Relationship

半城伤御伤魂 提交于 2019-12-07 20:26:58

问题


Why can i remove elements of a bidirectional relation although only one side of the relation is managed in persistence context (Example I)? When i have an unidirectional Relationship that doesn't work (see Example II). Why?

Entities:

@Entity
Class User {
    ...
    @OneToMany(mappedBy = "user")
    private List<Process> processes;

    @OneToOne // Unidirectional
    private C c;
    ...

    @PreRemove
    private void preRemove() {
        for (Process p : processes) {
            p.internalSetUser(null);
        }
    }
   ...
}

@Entity
Class Process {
    ...
    @ManyToOne
    private User user;
    ...

    @PreRemove
    protected void preRemove() {
        if (this.user != null) {
            user.internalRemoveProcess(this);
        }
    }
   ...
}

@Entity
Class C {

 }

Example I:

// Create User u1 with Processes p1, p2

tx.start();
// Only u1 is manged in persistence context and no process
userFacade.delete(u1); // There following is called: >> em.remove(em.merge(u1)); // Works
tx.commit();

Example II:

// Create User u and Object C c, establish their relation.

tx.start();
cFacade.remove(c); //>>MySQLIntegrityConstraintViolationException,foreign key constraint fails
ty.commit();

In the first example i use these internal methods to set in each case the other side of the relation but this other side is not managed in persistence context i think?! When i change a process of a user and save the user, the process is not updated unless i uses cascade.MERGE or if both are loaded in a transaction and therefor are managed in pc. So why does the removing work?


回答1:


In Example II, I guess you would have to call user.setC(null) before deleting the c.

In Example I, here is my understanding. You are first merging u1 so a u1' gets loaded into the PC and the state of u1 is copied to u1' (and that's all since you're not cascading MERGE), which is then returned. Then you call remove (on u1'), the preRemove gets called and changes p1' and p2'. They are thus dirty and will get updated appropriately on flush (setting the FK to NULL), while u1' will be deleted. And everything works.

Just in case, here are the semantics of the merge operation from the JPA 2.0 specification:

3.2.7.1 Merging Detached Entity State

The merge operation allows for the propagation of state from detached entities onto persistent entities managed by the entity manager.

The semantics of the merge operation applied to an entity X are as follows:

  • If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created.
  • If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new managed entity instance X'.
  • If X is a removed entity instance, an IllegalArgumentException will be thrown by the merge operation (or the transaction commit will fail).
  • If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been annotated with the cascade element value cascade=MERGE or cascade=ALL annotation.
  • For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
  • If X is an entity merged to X', with a reference to another entity Y, where cascade=MERGE or cascade=ALL is not specified, then navigation of the same association from X' yields a reference to a managed object Y' with the same persistent identity as Y.

The persistence provider must not merge fields marked LAZY that have not been fetched: it must ignore such fields when merging.

Any Version columns used by the entity must be checked by the persistence runtime implementation during the merge operation and/or at flush or commit time. In the absence of Version columns there is no additional version checking done by the persistence provider runtime during the merge operation.

Reference

  • JPA 2.0 Specification
    • 3.2.7.1 Merging Detached Entity State



回答2:


This is expected behavior:

As the relationship is bi-directional so as the application updates one side of the relationship, the other side should also get updated, and be in synch. In JPA, as in Java in general it is the responsibility of the application, or the object model to maintain relationships. If your application adds to one side of a relationship, then it must add to the other side.

This can be resolved through add or set methods in the object model that handle both sides of the relationships, so the application code does not need to worry about it. There are two ways to go about this, you can either only add the relationship maintenance code to one side of the relationship, and only use the setter from one side (such as making the other side protected), or add it to both sides and ensure you avoid a infinite loop.

For example:

public class Employee {
    private List phones;
    ...
    public void addPhone(Phone phone) {
        this.phones.add(phone);
        if (phone.getOwner() != this) {
            phone.setOwner(this);
        }
    }
    ...
}

Source: OneToMany#Getters_and_Setters



来源:https://stackoverflow.com/questions/3470841/jpa-removing-of-elements-of-a-bidirectional-relationship

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!