Un-à-plusieurs relations: mise à Jour à retirer les enfants avec JPA 2.0
J'ai une bidirectionnel un-à-plusieurs relations.
0 ou 1 client <-> Liste des 0 ou plus les commandes de produits.
Cette relation doit être défini ou indéfini sur deux entités:
Sur le côté client, je tiens à mettre la Liste des commandes de produits cédés au client; le client doit alors définir /annuler les ordres choisi automatiquement.
Sur le produit commande de côté, je veux mettre le client à qui l'oder est affecté; que la commande de produit doit ensuite être retiré de son assiged client de la liste et ajouté à la nouvelle attribué client de la liste.
Je veux l'utiliser pur JPA 2.0 annotations et une "fusion" appel à l'entité gestionnaire (avec cascade d'options). J'ai essayé avec le code suivant morceaux, mais il ne fonctionne pas (j'utilise EclipseLink 2.2.0 en tant que fournisseur de persistance)
@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
}
Façade de code pour la persistance de client:
//call setters on entity by JSF frontend...
getEntityManager().merge(client)
Façade de code pour la persistance de la commande de produit:
//call setters on entity by JSF frontend...
getEntityManager().merge(productOrder)
Lors de la modification de l'attribution du client sur le bon de commande côté, ça fonctionne bien: Sur le côté client, la commande est supprimée à partir du client précédent de la liste et est ajouté au nouveau client de la liste (si attribué).
MAIS lors de la modification sur le côté client, je ne peux qu'ajouter des commandes (de l'ordre de côté, de l'affectation au nouveau client est effectué), mais il l'ignore quand j'enlève les commandes du client la liste (après l'enregistrement et rafraîchissante, ils sont toujours dans la liste sur le côté client, et sur l'ordre de l'autre, ils sont également toujours attribué à la précédente client.
Juste pour clarifier, je NE souhaitez PAS utiliser un "supprimer" orphelins de l'option: Lors de la suppression d'une commande de la liste, il ne devrait pas être supprimé de la base de données, mais son attribution du client doit être mis à jour (qui est, null), comme défini dans le Client#setOrders méthode. Comment cela peut-il être engagés?
MODIFIER: Grâce à l'aide que j'ai reçu ici, j'ai été en mesure de résoudre ce problème. Voir ma solution ci-dessous:
Le client ("Un" /"propriété" côté) stocke les commandes qui ont été modifiés dans une temporaire terrain.
@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;
}
Sur le façade, lors de la persistance, il vérifie si il y a des entités qui doivent être conservées, trop. Notez que j'ai utilisé une interface pour encapsuler cette logique que ma façade est en fait générique.
//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);
}
}
OriginalL'auteur SputNick | 2012-05-17
Vous devez vous connecter pour publier un commentaire.
JPA ne pas faire cela, et autant que je sache, il n'y a pas d'implémentation JPA que ne ce soit. JPA vous oblige à gérer les deux côtés de la relation. Quand d'un seul côté de la relation est mise à jour c'est parfois dénommé "l'objet de la corruption"
JPA n'définir un "posséder" le côté dans une relation à double sens (pour un OneToMany c'est le côté qui n'a PAS de mappedBy annotation) qu'il utilise pour résoudre un conflit lors de la persistance de la base de données (il y a une seule représentation de cette relation dans la base de données par rapport aux deux en mémoire pour une résolution doit être faite). C'est pourquoi des changements à la ProductOrder classe sont réalisés, mais pas les modifications apportées à la classe Client.
Même avec le "propriétaire" de la relation, vous devez toujours mettre à jour des deux côtés. Cela amène souvent les gens à se fondant uniquement sur la mise à jour d'un seul côté et qu'ils obtiennent en difficulté quand ils tournent sur le cache de second niveau. En JPA les conflits mentionnés ci-dessus ne sont résolus lorsqu'un objet est persistant et rechargés à partir de la base de données. Une fois le 2ème niveau de cache est activé qui peut être de plusieurs opérations en bas de la route et en attendant, vous allez avoir affaire à un objet endommagé.
OriginalL'auteur Pace
Vous devez également fusionner les Commandes que vous avez supprimé, juste de fusionner le Client n'est pas suffisant.
Le problème est que si vous modifiez les Commandes qui ont été supprimés, vous n'êtes jamais à l'envoi de ces commandes sur le serveur, et de ne jamais l'appel de la fusion sur eux, il n'existe aucun moyen pour vous des changements soient pris en compte.
Vous devez appeler la fusion sur chaque Commande que vous retirez. Ou de traiter vos modifications locales, de sorte que vous n'avez pas besoin de sérialiser ou de fusionner des objets.
EclipseLink n'ont une relation bidirectionnelle fonction de maintenance qui peuvent travailler pour vous dans ce cas, mais il ne fait pas partie de la JPA.
OriginalL'auteur James
Une autre solution possible est d'ajouter la nouvelle propriété sur votre ProductOrder, j'ai nommé il
detached
dans les exemples suivants.Lorsque vous souhaitez détacher de la commande par le client, vous pouvez utiliser un rappel à l'ordre lui-même:
Au lieu de supprimer les commandes que vous souhaitez supprimer, vous définissez détaché de vrai. Lorsque vous vous engagez & rincer le client, l'entité gestionnaire de détecter la modification de l'ordre et de l'exécution de la @PreUpdate rappel efficacement le détachement de la commande par le client.
@PreRemove
annotation.OriginalL'auteur Jean