Hibernate - Une collection avec cascade = "all-delete-orphan" n'était plus référencée par l'instance d'entité propriétaire

225

Je rencontre le problème suivant lorsque j'essaie de mettre à jour mon entité:

"A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance".

J'ai une entité parent et elle a une Set<...>des entités enfants. Quand j'essaye de le mettre à jour, j'obtiens toutes les références à placer à ces collections et le place.

Le code suivant représente mon mappage:

@OneToMany(mappedBy = "parentEntity", fetch = FetchType.EAGER)
@Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
public Set<ChildEntity> getChildren() {
    return this.children;
}

J'ai essayé de nettoyer l'ensemble <..> uniquement, en fonction de ceci: comment résoudre "possible" le problème, mais cela n'a pas fonctionné.

Si vous avez des idées, faites-le moi savoir.

Merci!

axcdnt
la source
1
@ mel3kings, le lien que vous avez fourni n'est plus actif.
Opale
essayez d'utiliser des collections mutables lors de la suppression d'éléments. Par exemple, n'utilisez pas something.manyother.remove(other)si manyotherest un List<T>. Faire beaucoup d'autres Mutable, aimer ArrayList<T>et utiliserorphanDelete = true
nurettin

Réponses:

220

Vérifiez tous les endroits où vous attribuez quelque chose à sonEntities. Le lien que vous avez référencé indique clairement la création d'un nouveau HashSet, mais vous pouvez rencontrer cette erreur chaque fois que vous réaffectez l'ensemble. Par exemple:

public void setChildren(Set<SonEntity> aSet)
{
    this.sonEntities = aSet; //This will override the set that Hibernate is tracking.
}

Habituellement, vous ne voulez que "nouveau" l'ensemble une fois dans un constructeur. Chaque fois que vous souhaitez ajouter ou supprimer quelque chose à la liste, vous devez modifier le contenu de la liste au lieu d'attribuer une nouvelle liste.

Pour ajouter des enfants:

public void addChild(SonEntity aSon)
{
    this.sonEntities.add(aSon);
}

Pour retirer des enfants:

public void removeChild(SonEntity aSon)
{
    this.sonEntities.remove(aSon);
}
brainimus
la source
8
En fait, mon problème concernait les égaux et le code de hachage de mes entités. Un code hérité peut apporter beaucoup de problèmes, n'oubliez jamais de le vérifier. Tout ce que j'ai fait, c'est de conserver la stratégie de suppression des orphelins et de corriger les égaux et le code de hachage.
axcdnt
6
Je suis content que vous ayez résolu votre problème. equals et hashcode m'ont mordu plusieurs fois avec Hibernate. Au lieu de mettre à jour le titre de la question avec "[Résolu]", vous devriez continuer et publier votre réponse, puis la marquer comme réponse acceptée.
brainimus
Merci, je suis tombé sur quelque chose de similaire et il s'est avéré que mon setter pour ce Set était vide ..
Siddhartha
oui, même sur des listes vides, vous obtenez cette erreur. je ne m'attendrais pas à cela car il n'y a pas d'orphelins (la liste était vide).
tibi
4
Habituellement, vous ne voulez "nouveau" l'ensemble qu'une seule fois dans un constructeur. Chaque fois que vous souhaitez ajouter ou supprimer quelque chose à la liste, vous devez modifier le contenu de la liste au lieu d'attribuer une nouvelle liste. Most imp
Nikhil Sahu
109

La méthode:

public void setChildren(Set<SonEntity> aSet) {
    this.sonEntities = aSet;
}

fonctionne si le parentEntityest détaché et à nouveau si nous le mettons à jour.
Mais si l'entité n'est pas détachée de chaque contexte (c'est-à-dire que les opérations de recherche et de mise à jour sont dans la même transaction), la méthode ci-dessous fonctionne.

public void setChildren(Set<SonEntity> aSet) {
    //this.sonEntities = aSet; //This will override the set that Hibernate is tracking.
    this.sonEntities.clear();
    if (aSet != null) {
        this.sonEntities.addAll(aSet);
    }
}
Manu
la source
1
@Skuld J'ai un problème similaire et j'ai appliqué votre solution (dans la méthode de définition, j'efface la collection d'enfants - this.children.clear () - et j'ai ajouté les nouveaux enfants - this.children.addAll (children)). Cette modification n'a pas résolu mon problème. J'obtiens toujours l'exception "Une collection avec cascade =" all-delete-orphan "n'était plus référencée par l'instance d'entité propriétaire". Avez-vous une idée pourquoi? Merci beaucoup!
ovdsrn
@ovdsrn Désolé, ce n'est pas ma réponse, je viens de trier la mise en forme de la réponse, l'auteur d'origine (kmmanu) pourrait vous aider (ou alt, vous voudrez peut-être commencer une nouvelle question si votre scénario est différent de la question d'origine posée ici) Bonne chance
Skuld
1
Cela fonctionne bien, mais pas si bien lorsque les enfants sont contenus dans un composant hibernate imbriqué et que le composant est rendu nul dans son entité. Ensuite, vous obtenez la même erreur. Cela est dû au fait que le propriétaire des enfants est l'entité racine et non le composant qui est rendu nul ... A tel, un composant n'est jamais autorisé à devenir nul, mais devrait plutôt entraîner un transfert vers une méthode destroy () dans le enfant ... Au moins, je ne connais pas de meilleure solution ... C'est une sorte de construction de collection clear () mais ensuite sur un composant via destroy () ...
edbras
Voir aussi cet article pour plus de détails sur ce qui précède: stackoverflow.com/questions/4770262/…
edbras
7
utilisez this.sonEntities.retainAll (aSet) sur sonEntities.clear () car si aSet == this.sonEntities (c'est-à-dire le même objet), vous effacerez l'ensemble avant de l'ajouter!
Martin
33

Quand j'ai lu à divers endroits que hibernation n'aimait pas que vous assigniez à une collection, j'ai supposé que la chose la plus sûre à faire serait évidemment de la rendre finale comme ceci:

class User {
  private final Set<Role> roles = new HashSet<>();

public void setRoles(Set<Role> roles) {
  this.roles.retainAll(roles);
  this.roles.addAll(roles);
}
}

Cependant, cela ne fonctionne pas et vous obtenez l'erreur redoutée "n'est plus référencée", ce qui est en fait assez trompeur dans ce cas.

Il s'avère que hibernate appelle votre méthode setRoles ET veut que sa classe de collection spéciale soit installée ici, et n'acceptera pas votre classe de collection. Cela m'a laissé perplexe pendant longtemps, malgré la lecture de tous les avertissements concernant la non-affectation à votre collection dans votre méthode définie.

J'ai donc changé pour ceci:

public class User {
  private Set<Role> roles = null;

  public void setRoles(Set<Role> roles) {
  if (this.roles == null) {
    this.roles = roles;
  } else {
    this.roles.retainAll(roles);
   this.roles.addAll(roles);
  }
}
}

Ainsi, lors du premier appel, hibernate installe sa classe spéciale, et lors des appels suivants, vous pouvez utiliser la méthode vous-même sans tout détruire. Si vous voulez utiliser votre classe comme bean, vous avez probablement besoin d'un setter fonctionnel, et cela semble au moins fonctionner.

xpusostomos
la source
2
Pourquoi appelez-vous retenueAll ()? Pourquoi pas clear () suivi de addAll ()?
edbras
1
"Pourquoi appelez-vous retenueAll ()? Pourquoi pas clear () suivi de addAll ()?" Bonne question, je pense que je travaillais sous l'hypothèse que l'hibernation serait moins susceptible de voir cela comme une mise à jour de la base de données si vous ne supprimiez pas les éléments avant de les ajouter à nouveau. Mais je soupçonne que cela ne fonctionne pas de cette façon de toute façon.
xpusostomos
2
Vous devez utiliser retenueAll () sur clear () sinon vous pouvez effacer les rôles s'il vous arrive de passer dans l'objet set identique. Par exemple: user.setRoles (user.getRoles ()) == user.roles.clear ()
Martin
2
Lorsque vous définissez orphanRemoval = true et que vous créez un enregistrement dans lequel ces collections sont nulles, vous obtenez également cette erreur. Donc: a a oneToMany b avec orphanremoval = true. Lorsque vous créez A où B = null, ce problème est déclenché. Votre solution pour initialiser et rendre définitive semble la meilleure.
Lawrence
Je vous remercie! J'initialisais ma liste comme List<String> list = new ArrayList<>();. Le changer pour List<String> list = null;résoudre le problème :)
Radical
19

En fait, mon problème concernait les égaux et le code de hachage de mes entités. Un code hérité peut apporter beaucoup de problèmes, n'oubliez jamais de le vérifier. Tout ce que j'ai fait, c'est de conserver la stratégie de suppression des orphelins et de corriger les égaux et le code de hachage.

axcdnt
la source
9
s'il vous plaît, un exemple d'un égal bien fait et des méthodes hashCode? parce que j'ai beaucoup de problèmes: ou je ne peux pas mettre à jour mon jeu ou je reçois une erreur StackOverflow. j'ai ouvert une question ici: stackoverflow.com/questions/24737145/… . merci
SaganTheBest
11

J'ai eu la même erreur. Le problème pour moi était qu'après avoir enregistré l'entité, la collection mappée était toujours nulle et lors de la tentative de mise à jour de l'entité, l'exception était levée. Ce qui m'a aidé: enregistrer l'entité, puis faire un rafraîchissement (la collection n'est plus nulle) et ensuite effectuer la mise à jour. Peut-être que l'initialisation de la collection avec un nouveau ArrayList () ou quelque chose pourrait aussi aider.

Konsumierer
la source
L'envoi d'une nouvelle ArrayList au lieu de null, a fonctionné pour moi. Merci
rpajaziti
4

A UN TYPE DE RELATION:


N'essayez pas d'instancier la collection lorsqu'elle est déclarée dans hasMany, ajoutez et supprimez simplement des objets.

class Parent {
    static hasMany = [childs:Child]
}

UTILISER LE TYPE DE RELATION:


Mais la collection ne peut être nulle que lorsqu'elle est déclarée en tant que propriété (relation d'utilisation) et n'est pas initialisée dans la déclaration.

class Parent {
    List<Child> childs = []
}
IgniteCoders
la source
4

J'ai utilisé l'approche @ user2709454 avec une petite amélioration.

public class User {
    private Set<Role> roles;

    public void setRoles(Set<Role> roles) {
        if (this.roles == null) {
            this.roles = roles;
        } else if(this.roles != roles) { // not the same instance, in other case we can get ConcurrentModificationException from hibernate AbstractPersistentCollection
            this.roles.clear();
            if(roles != null){
                this.roles.addAll(roles);
            }
        }
    }
}
luwojtaszek
la source
3

J'ai eu ce problème lors de l'utilisation TreeSet. J'ai initialisé oneToManyavec TreeSetquels travaux

@OneToMany(mappedBy = "question", fetch = FetchType.EAGER, cascade = { CascadeType.ALL }, orphanRemoval=true)
@OrderBy("id")
private Set<WizardAnswer> answers = new TreeSet<WizardAnswer>();

Mais cela apportera l'erreur décrite questionci - dessus. Il semble donc que hibernatepris en charge SortedSetet si l'on change simplement la ligne ci-dessus pour

@OneToMany(mappedBy = "question", fetch = FetchType.EAGER, cascade = { CascadeType.ALL }, orphanRemoval=true)
@OrderBy("id")
private SortedSet<WizardAnswer> answers;

ça marche comme par magie :) plus d'infos sur hibernate SortedSetpeut être ici

chêne
la source
Peut-être, il vaut mieux utiliser Collections.emptySet () pour utiliser une liste vide de singleton
ryzhman
3

La seule fois où j'obtiens cette erreur, c'est lorsque j'essaie de passer NULL dans le setter pour la collection. Pour éviter cela, mes setters ressemblent à ceci:

public void setSubmittedForms(Set<SubmittedFormEntity> submittedForms) {
    if(submittedForms == null) {
        this.submittedForms.clear();
    }
    else {
        this.submittedForms = submittedForms;
    }
}
Arlo Guthrie
la source
3

J'ai rencontré cela lors de la mise à jour d'une entité avec une demande de publication JSON. L'erreur s'est produite lorsque j'ai mis à jour l'entité sans données sur les enfants, même lorsqu'il n'y en avait pas. Ajouter

"children": [],

au corps de la demande a résolu le problème.

co ting
la source
1

Une autre cause peut être l'utilisation de lombok.

@Builder- fait économiser Collections.emptyList()même si vous dites.myCollection(new ArrayList());

@Singular- ignore les valeurs par défaut du niveau de classe et laisse le champ nullmême si le champ de classe a été déclaré commemyCollection = new ArrayList()

Mes 2 cents, je viens de passer 2 heures avec le même :)

Jan Zyka
la source
1

Je devenais A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instancequand je me mettais parent.setChildren(new ArrayList<>()). Quand j'ai changé pourparent.getChildren().clear() , cela a résolu le problème.

Vérifiez pour plus de détails: HibernateException - Une collection avec cascade = "all-delete-orphan" n'était plus référencée par l'instance d'entité propriétaire .

Tout comme
la source
1

J'utilise Spring Boot et j'ai eu ce problème avec une collection, bien que je ne l'écrase pas directement, car je déclare un champ supplémentaire pour la même collection avec un sérialiseur et désérialiseur personnalisé afin de fournir une représentation plus conviviale du Les données:

  public List<Attribute> getAttributes() {
    return attributes;
  }

  public void setAttributes(List<Attribute> attributes) {
    this.attributes = attributes;
  }

  @JsonSerialize(using = AttributeSerializer.class)
  public List<Attribute> getAttributesList() {
    return attributes;
  }

  @JsonDeserialize(using = AttributeDeserializer.class)
  public void setAttributesList(List<Attribute> attributes) {
    this.attributes = attributes;
  }

Il semble que même si je n'écrase pas la collection moi - même , la désérialisation le fait sous le capot, ce qui déclenche tout de même ce problème. La solution était de changer le setter associé au désérialiseur afin qu'il efface la liste et ajoute tout, plutôt que de l'écraser:

  @JsonDeserialize(using = AttributeDeserializer.class)
  public void setAttributesList(List<Attribute> attributes) {
    this.attributes.clear();
    this.attributes.addAll(attributes);
  }
Sofia Paixão
la source
1

J'ai eu le même problème, mais c'était lorsque l'ensemble était nul. Uniquement dans la collection Set dans la recherche de travail List. Vous pouvez essayer d'annoter la mise en veille prolongée @LazyCollection (LazyCollectionOption.FALSE) au lieu de l'annotation JPA fetch = FetchType.EAGER.

Ma solution: c'est ma configuration et ça marche bien

@OneToMany(mappedBy = "format", cascade = CascadeType.ALL, orphanRemoval = true)
@LazyCollection(LazyCollectionOption.FALSE)
private Set<Barcode> barcodes;

@OneToMany(mappedBy = "format", cascade = CascadeType.ALL, orphanRemoval = true)
@LazyCollection(LazyCollectionOption.FALSE)
private List<FormatAdditional> additionals;
Dario Gaston
la source
1
@OneToMany(mappedBy = 'parent', cascade= CascadeType.ALL, orphanRemoval = true)
List<Child> children = new ArrayList<>();

J'ai rencontré la même erreur lorsque j'ajoutais un objet enfant à la liste existante d'objets enfants.

childService.saveOrUpdate(child);
parent.addToChildren(child);
parentService.saveOrUpdate(parent);

Ce qui a résolu mon problème passe à:

child = childService.saveOrUpdate(child);

Maintenant, l'enfant est ravivé avec d'autres détails et cela a bien fonctionné.

Neha Gupta
la source
0

Ajout de ma réponse stupide. Nous utilisons Spring Data Rest. C'était notre relation assez standard. Le modèle a été utilisé ailleurs.

//Parent class
@OneToMany(mappedBy = 'parent', 
           cascade= CascadeType.ALL, orphanRemoval = true)
@LazyCollection(LazyCollectionOption.FALSE)
List<Child> children = new LinkedList<>()


//Child class
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = 'ParentID', updatable = false)
@JsonBackReference
Parent parent

Avec la relation que nous avons créée, il était toujours prévu que les enfants soient ajoutés par le biais de leur propre repo. Je n'avais pas encore ajouté le repo. Le test d'intégration que nous avons effectué passait par un cycle de vie complet de l'entité via des appels REST afin que les transactions se ferment entre les demandes. Aucun repo pour l'enfant signifiait que le json avait les enfants dans le cadre de la structure principale au lieu d'en _embedded. Les mises à jour du parent entraîneraient alors des problèmes.

Snekse
la source
0

La solution suivante a fonctionné pour moi

//Parent class
@OneToMany(mappedBy = 'parent', 
           cascade= CascadeType.ALL, orphanRemoval = true)
@OrderBy(value="ordinal ASC")
List<Child> children = new ArrayList<>()

//Updated setter of children 
public void setChildren(List<Children> children) {
    this.children.addAll(children);
    for (Children child: children)
        child.setParent(this);
}


//Child class
@ManyToOne
@JoinColumn(name="Parent_ID")
private Parent parent;
priyanka_rao
la source
0

Au lieu d'attribuer une nouvelle collection

public void setChildren(Set<ChildEntity> children) {
    this.children = children;
}

Remplacer tous les éléments par

public void setChildren(Set<ChildEntity> children) {
    Collections.replaceAll(this.children,children);
}
Marcin Szymczak
la source
0

attention à

BeanUtils.copyProperties(newInsum, insumOld,"code");

Cette méthode brise également l'hibernation.

Diógenes Ricardo
la source
0

Le mien était complètement différent avec Spring Boot! Pour moi, ce n'était pas dû à la définition de la propriété de la collection.

Dans mes tests, j'essayais de créer une entité et j'obtenais cette erreur pour une autre collection qui n'était pas utilisée!

Après tant d'essais, je viens d'ajouter un @Transactionalsur la méthode de test et il l'a résolu. Mais n'en avez pas la raison.

madz
la source
0

Ceci est en contraste avec les réponses précédentes, j'ai eu exactement la même erreur: "Une collection avec cascade =" all-delete-orphan "n'était plus référencée ...." quand ma fonction setter ressemblait à ceci:

public void setTaxCalculationRules(Set<TaxCalculationRule> taxCalculationRules_) {
    if( this.taxCalculationRules == null ) {
        this.taxCalculationRules = taxCalculationRules_;
    } else {
        this.taxCalculationRules.retainAll(taxCalculationRules_);
        this.taxCalculationRules.addAll(taxCalculationRules_);
    }
}

Et puis il a disparu quand je l'ai changé en version simple:

public void setTaxCalculationRules(Set<TaxCalculationRule> taxCalculationRules_) {
    this.taxCalculationRules = taxCalculationRules_;
}

(versions d'hibernation - essayé à la fois 5.4.10 et 4.3.11. Passé plusieurs jours à essayer toutes sortes de solutions avant de revenir à l'affectation simple dans le setter. Confus maintenant quant à pourquoi cela.)

ligett
la source