问题
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 ofX
is copied onto a pre-existing managed entity instanceX'
of the same identity or a new managed copyX'
ofX
is created.- If
X
is a new entity instance, a new managed entity instanceX'
is created and the state ofX
is copied into the new managed entity instanceX'
.- If
X
is a removed entity instance, anIllegalArgumentException
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 fromX
if these relationships have been annotated with the cascade element valuecascade=MERGE
orcascade=ALL
annotation.- For all entities
Y
referenced by relationships fromX
having the cascade element valuecascade=MERGE
orcascade=ALL
,Y
is merged recursively asY'
. For all suchY
referenced byX
,X'
is set to referenceY'
. (Note that ifX
is managed thenX
is the same object asX'
.)- If
X
is an entity merged toX'
, with a reference to another entityY
, wherecascade=MERGE
orcascade=ALL
is not specified, then navigation of the same association fromX'
yields a reference to a managed objectY'
with the same persistent identity asY
.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 ofVersion
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