Eins-zu-viele-Beziehung: Update entfernt Kinder mit JPA 2.0
Habe ich eine bidirektionale eins-zu-viele-Beziehung.
0 oder 1 client <-> Liste von 0 oder mehr Produkt-Bestellungen.
Beziehung gesetzt werden sollten, oder unset auf beide Entitäten:
Auf der client-Seite, ich möchte, um die Liste der Bestellungen von Produkten, die der client zugewiesen; der client sollte dann set /unset um die Bestellungen automatisch gewählt.
Auf der Produkt-Seite, um, ich möchte den client, zu dem der oder zugewiesen; das Produkt bestellen, sollten dann entfernt werden aus den zuvor assiged client-Liste Hinzugefügt und der neue zugewiesene client-Liste.
Will ich mit reinen JPA 2.0 annotations und ein "merge" - Aufruf der entity-manager (nur mit cascade-Optionen). Ich habe versucht mit dem folgenden code-Stück, aber es funktioniert nicht (ich benutze EclipseLink 2.2.0 als Persistenz-provider)
@Entity
public class Client implements Serializable {
@OneToMany(mappedBy = "client", cascade= CascadeType.ALL)
private List<ProductOrder> orders = new ArrayList<>();
public void setOrders(List<ProductOrder> orders) {
for (ProductOrder order : this.orders) {
order.unsetClient();
//don't use order.setClient(null);
//(ConcurrentModificationEx on array)
//TODO doesn't work!
}
for (ProductOrder order : orders) {
order.setClient(this);
}
this.orders = orders;
}
//other fields /getters /setters
}
@Entity
public class ProductOrder implements Serializable {
@ManyToOne(cascade= CascadeType.ALL)
private Client client;
public void setClient(Client client) {
//remove from previous client
if (this.client != null) {
this.client.getOrders().remove(this);
}
this.client = client;
//add to new client
if (client != null && !client.getOrders().contains(this)) {
client.getOrders().add(this);
}
}
public void unsetClient() {
client = null;
}
//other fields /getters /setters
}
Fassade code für die Persistenz-client:
//call setters on entity by JSF frontend...
getEntityManager().merge(client)
Fassade code für die Persistenz Produkt Reihenfolge:
//call setters on entity by JSF frontend...
getEntityManager().merge(productOrder)
Beim ändern der client-Einsatz auf der um Seite, es funktioniert gut: Auf der client-Seite, wird die Bestellung entfernt von der vorherigen client-Liste und wird Hinzugefügt, um die neuen client-Liste (falls zugewiesen).
ABER beim Wechsel auf der client-Seite, kann ich nur hinzufügen, Bestellungen (auf der Bestellseite, Zuordnung zu den neuen client ausgeführt wird), aber es einfach ignoriert, wenn ich Aufträge entfernen aus der Liste des Clients (nach dem speichern und erfrischend, Sie sind immer noch in der Liste auf der client-Seite, und auf der um Seite, Sie sind auch noch zugewiesen, um den vorherigen client.
Nur zur Klarstellung, ich NICHT verwenden möchten, ein "delete-orphan" - option: Beim entfernen eines Auftrags aus der Liste, sollte es nicht aus der Datenbank gelöscht werden, sondern seine Zuordnung client aktualisiert werden soll (also auf null), wie definiert in den Client#setOrders Methode. Wie kann dies werden erreicht?
BEARBEITEN: Dank der Hilfe, die ich hier hatte, war ich in der Lage, dieses problem zu beheben. Siehe meine Lösung unter:
Client ("Ein" /"Eigentum" Seite) speichert die Bestellungen, die geändert wurden, werden in ein temporäres Feld.
@Entity
public class Client implements Serializable, EntityContainer {
@OneToMany(mappedBy = "client", cascade= CascadeType.ALL)
private List<ProductOrder> orders = new ArrayList<>();
@Transient
private List<ProductOrder> modifiedOrders = new ArrayList<>();
public void setOrders(List<ProductOrder> orders) {
if (orders == null) {
orders = new ArrayList<>();
}
modifiedOrders = new ArrayList<>();
for (ProductOrder order : this.orders) {
order.unsetClient();
modifiedOrders.add(order);
//don't use order.setClient(null);
//(ConcurrentModificationEx on array)
}
for (ProductOrder order : orders) {
order.setClient(this);
modifiedOrders.add(order);
}
this.orders = orders;
}
@Override //defined by my EntityContainer interface
public List getContainedEntities() {
return modifiedOrders;
}
Auf dem Fassade, wenn anhaltende, prüft es, ob es irgendwelche Personen, dass muss beibehalten werden, auch. Beachten Sie, dass ich verwendet eine Schnittstelle zu Kapseln diese Logik wie meine Fassade ist tatsächlich generic.
//call setters on entity by JSF frontend...
getEntityManager().merge(entity);
if (entity instanceof EntityContainer) {
EntityContainer entityContainer = (EntityContainer) entity;
for (Object childEntity : entityContainer.getContainedEntities()) {
getEntityManager().merge(childEntity);
}
}
InformationsquelleAutor SputNick | 2012-05-17
Du musst angemeldet sein, um einen Kommentar abzugeben.
JPA tut dies nicht und soweit ich weiß, es ist keine JPA-Implementierung, tut dies entweder. JPA verlangt, dass Sie verwalten beide Seiten der Beziehung. Wenn nur die eine Seite der Beziehung wird aktualisiert, dies wird manchmal bezeichnet als "Objekt der Korruption"
JPA nicht definiert "owning" - Seite in einem zwei-Wege-Beziehung (für einen OneToMany dies ist die Seite, die NICHT über das mappedBy Annotationen), die Sie nutzt, einen Konflikt zu klären, wenn das verharren in der Datenbank (es gibt nur eine Art der Darstellung dieser Beziehungen in der Datenbank verglichen, um die beiden in den Speicher, so dass eine Auflösung gemacht werden müssen). Dies ist der Grund, warum änderungen der ProductOrder Klasse realisiert werden, nicht aber änderungen an der Client-Klasse.
Sogar mit dem "Besitz" Beziehung, die Sie sollten update immer beide Seiten. Dies führt oft Menschen verlassen sich nur auf die Aktualisierung einer Seite und bekommen Sie ärger, wenn Sie schalten Sie den second-level-cache. In JPA die Konflikte, die oben erwähnt werden nur aufgelöst, wenn ein Objekt persistent ist und neu geladen aus der Datenbank. Einmal die 2nd-level-cache aktiviert ist, kann mehrere Transaktionen, die Straße runter, und in der Zwischenzeit werden Sie den Umgang mit einem beschädigten Objekt.
InformationsquelleAutor Pace
Haben Sie auch die Zusammenführung der Aufträge, die Sie entfernt, nur die Zusammenlegung der Client ist nicht genug.
Das Problem ist, dass Sie zwar ändern sich die Aufträge, die entfernt wurden, sind Sie nie senden diese Aufträge an den server, und nie aufrufen von merge-auf Sie, so gibt es keine Möglichkeit für Sie änderungen übernommen werden.
Müssen Sie rufen Sie vereinigen sich auf jede Bestellung, die Sie entfernen. Oder Bearbeiten Sie Ihre änderungen lokal, so brauchen Sie nicht zu serialisieren oder merge-Objekte.
EclipseLink hat eine bidirektionale Beziehung-Wartung", die bei Ihnen in diesem Fall, aber es ist nicht Teil der JPA.
InformationsquelleAutor James
Andere mögliche Lösung ist, fügen Sie die neue Eigenschaft auf Ihre ProductOrder, ich nannte es
detached
in den folgenden Beispielen.Wenn Sie möchten, trennen Sie die Bestellung des Kunden können Sie mit einem Rückruf bei der Bestellung selbst:
Anstatt zu löschen die Aufträge, die Sie löschen möchten, setzen Sie losgelöst zu wahren. Wenn Sie merge & Spülen Sie die client-entity-manager erkennt die geänderte Reihenfolge und ausführen der @PreUpdate Rückruf effektiv abnehmen der Auftrag des Kunden.
@PreRemove
annotation.InformationsquelleAutor Jean