J'ai une collection (liste) @OneToMany que je voudrais effacer et ajouter de nouveaux éléments à la même transaction.
En utilisant
collection.clear();
collection.add(new EntityB());
Ajoute simplement la nouvelle instance et ne supprime jamais rien. J'ai orphanRemoval = true
pour le champ de collecte.
AJOUTÉE:
// Parent entity
@OneToMany(mappedBy = "product", orphanRemoval = true)
private List<Feature> features = new ArrayList<>();
// Child entity
@ManyToOne(cascade = CascadeType.ALL)
private Product product;
// Clear and add attempt
product.getFeatures().clear();
Feature feature = new Feature(product, ls);
product.getFeatures().add(feature);
Il s'avère que la solution actuelle utilisait une annotation @JoinColumn au lieu du paramètre mappedBy = "".
Vous essayez d'effacer un seul côté de l'association bidirectionnelle.
Donc au lieu de:
collection.clear();
Comme expliqué dans cet article , essayez de nettoyer les deux côtés et cela devrait fonctionner:
for(Iterator<Feature> featureIterator = features.iterator();
featureIterator.hasNext(); ) {
Feature feature = featureIterator .next();
feature.setProduct(null);
featureIterator.remove();
}
En outre, supprimez la cascade de @ManyToOne
et déplacez-la vers @OneToMany
.
Cependant, si vous avez une contrainte unique, cet clear + add
Anti-Pattern ne fonctionnera pas car l'action INSERT est exécutée avant celle DELETE, comme expliqué dans cet article .
La bonne façon de le faire est de vérifier quelles entrées doivent être supprimées et de simplement les supprimer. Ajoutez ensuite les nouveaux et mettez à jour ceux qui ont été modifiés. Voici comment vous faites une fusion de collection correctement.
Cela semble vraiment être un bogue dans de nombreuses versions d'Hibernate. Je l'ai testé avec EclipseLink et cela fonctionne sans problème.
Solution de contournement sous dans Hibernate (testé sous Hibernate 4.3.6-Final): supprimez toute cascade dans l'entité Feature
et ajoutez CascadeType.PERSIST
(ou CascadeType.ALL
) dans l'entité Product
.
Juste pour vous assurer que cela ne fonctionne pas, essayez ce qui suit:
EntityManager em = ...//fetch the entitymanager. If a Container-managed transaction, you already got it injected
em.getTransaction().begin();//only if resource-local persistence unit. Otherwise if JTA: open the transaction the JTA-specific way (if that was not already done by the container)
Product product = em.find(Product.class, productId);
for (Feature crtFeature : product.getFeatures()) {
if (!em.contains(crtFeature)) {
throw new RuntimeException("Feature is not managed, so removeOrpahns cannot work");
}
}
product.getFeatures().clear();
Feature feature = new Feature(product, ls);
em.persist(feature);//you need this, as there is no cascading from Product to Feature.
product.getFeatures().add(feature);
em.getTransaction().commit();//if you work with a resource-local persistence unit. Otherwise if JTA: commit the transaction the JTA-specific way (if that was not already done by the container)
J'ai fait face à un problème similaire récemment. Pour moi, le problème était que les orphelins étaient toujours référencés à partir d'une autre entité gérée et qu'un cascading PERSIST avait été défini pour cette relation:
// Parent entity
@OneToMany(mappedBy = "product", orphanRemoval = true)
private List<Feature> features = new ArrayList<>();
// Child entity
@ManyToOne
private Product product;
@ManyToOne
private Description description;
// Another entity (let's say descriptions can be shared between features)
@OneToMany(mappedBy = "description", cascade = CascadeType.PERSIST)
private List<Feature> features = new ArrayList<>();
Supposons que toutes les entités impliquées soient gérées, c'est-à-dire chargées dans le contexte de persistance. Maintenant, nous faisons la même chose que l'OP:
// Clear and add attempt
product.getFeatures().clear();
Feature feature = new Feature(product, ls);
product.getFeatures().add(feature);
Philosophiquement, le problème ici est que le modèle d'objet devient incohérent si vous supprimez uniquement la fonction de l'entité Product, mais pas de l'entité Description. Après tout, vous souhaitez que la fonctionnalité soit supprimée, mais elle est toujours référencée à partir d'autres objets. Techniquement, il se produit qu'il y a deux cascades en conflit qui vont à l'entité Feature, et le résultat peut dépendre de l'ordre dans lequel elles sont appliquées.
Comme la fonctionnalité a été supprimée de la collection dans le produit, la suppression Orphan s'applique et l'entité Feature passe à l'état "supprimé" lors du prochain vidage, comme indiqué dans la spécification JPA 2.1 (2.9). J'ai mis l'accent sur les parties pertinentes:
Les associations spécifiées comme OneToOne ou OneToMany prennent en charge l'utilisation de De l'option orphanRemoval. Les comportements suivants s'appliquent lorsque OrphanRemoval est en vigueur:
- Si une entité qui est la cible de la relation Est supprimée de la relation (en définissant la relation Sur null ou en supprimant l'entité de la collection de relations ), L'opération de suppression sera appliqué à l'entité étant orpheline. L'opération de suppression est appliquée au moment de l'opération de vidage . La fonctionnalité orphanRemoval est destinée aux entités Qui sont «détenues» en privé par leur entité mère. Les applications portables Ne doivent par ailleurs pas dépendre d'un ordre spécifique de suppression de Et ne doivent pas réaffecter une entité orpheline à Une autre relation ou sinon, essayez de le persister . Si l'entité Orpheline est une entité détachée, nouvelle ou supprimée, la sémantique de OrphanRemoval ne s'applique pas.
Toutefois, la même entité est toujours référencée à partir d'une entité Description, qui possède un PERSIST en cascade vers l'entité. La spécification JPA 2.1 indique ce qui suit:
La sémantique de l'opération de vidage, appliquée à une entité X, est la suivante :
Si X est une entité gérée, elle est synchronisée avec la base de données .
- Pour toutes les entités Y référencées par une relation de X, si La relation à Y a été annotée avec l'élément en cascade Valeur cascade = PERSIST ou cascade = ALL, l'opération de persistance est appliquée jouet.
Donc, cette cascade va effectuer une opération "persistante" sur l'entité Feature, même si nous n'appelons pas em.persist () dans la description. Il suffit que la description soit gérée lorsqu’un vidage est effectué pour déclencher cette cascade persistante.
Cela signifie que nous faisons exactement ce que la spécification nous disait de ne pas faire: effectuer la suppression d'Orphan et persister dans la même entité. Ce qui semble se produire dans la pratique chez Hibernate, c'est que les deux opérations sont appliquées à tour de rôle. L’opération remove commence par faire passer l’entité Feature à l’état «remove», puis l’opération persistante transforme l’entité supprimée en une entité gérée. Par conséquent, la fonctionnalité n'est pas supprimée de la base de données.
Dans la section 2.9, Relations entre entités, la spécification JPA 2.1 indique:
Si l'entité orpheline est une entité détachée, nouvelle ou supprimée, , La sémantique de orphanRemoval ne s'applique pas.
Êtes-vous sûr que votre entité est gérée dans le contexte de persistance lors de sa suppression de la collection?
Vous pouvez le réparer en utilisant fetch=fetchType.EAGER
ou par fetch joins
. Alternativement (cela dépend de votre cas d'utilisation), il peut être suffisant de définir l'option cascade
appropriée.