Hibernate: la instancia de la entidad propietaria ya no hace referencia a una colección con cascade = "all-delete-huérfano"

225

Tengo el siguiente problema al intentar actualizar mi entidad:

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

Tengo una entidad principal y tiene una Set<...>de algunas entidades secundarias. Cuando intento actualizarlo, obtengo todas las referencias que se establecerán en estas colecciones y lo configuro.

El siguiente código representa mi mapeo:

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

He tratado de limpiar el Set <..> solamente, de acuerdo con esto: Cómo "posible" resolver el problema, pero no funcionó.

Si tiene alguna idea, hágamelo saber.

¡Gracias!

axcdnt
fuente
1
@ mel3kings, el enlace que proporcionó ya no está activo.
Opal
intente usar colecciones mutables al eliminar elementos. Por ejemplo, no use something.manyother.remove(other)if manyotheres a List<T>. Hacer muchos otros mutables, como ArrayList<T>y usarorphanDelete = true
nurettin

Respuestas:

220

Verifique todos los lugares donde está asignando algo a sonEntities. El enlace al que hizo referencia señala claramente la creación de un nuevo HashSet, pero puede tener este error cada vez que reasigna el conjunto. Por ejemplo:

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

Por lo general, solo desea "nuevo" el conjunto una vez en un constructor. Cada vez que desee agregar o eliminar algo a la lista, debe modificar el contenido de la lista en lugar de asignar una nueva lista.

Para agregar niños:

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

Para eliminar niños:

public void removeChild(SonEntity aSon)
{
    this.sonEntities.remove(aSon);
}
Brainimus
fuente
8
En realidad, mi problema era sobre iguales y hashcode de mis entidades. Un código heredado puede traer muchos problemas, nunca olvides revisarlo. Todo lo que hice fue mantener la estrategia de borrar huérfanos y corregir los iguales y el código hash.
axcdnt
66
Me alegra que hayas resuelto tu problema. equals y hashcode me han mordido algunas veces con Hibernate. En lugar de actualizar el título de la pregunta con "[Resuelto]", debe continuar y publicar su respuesta y luego marcarla como la respuesta aceptada.
brainimus
Gracias, me encontré con algo similar y resultó que mi setter para ese Set estaba vacío ...
Siddhartha
Sí, incluso en las listas vacías aparece este error. No esperaría eso porque no hay huérfanos (la lista estaba vacía).
tibi
44
Por lo general, solo desea "nuevo" el conjunto una vez en un constructor. Cada vez que desee agregar o eliminar algo a la lista, debe modificar el contenido de la lista en lugar de asignar una nueva lista. Más diablillo
Nikhil Sahu
109

El método:

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

funciona si parentEntityestá separado y nuevamente si lo actualizamos.
Pero si la entidad no está separada de cada contexto (es decir, las operaciones de búsqueda y actualización están en la misma transacción), el método siguiente funciona.

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
fuente
1
@Skuld Tengo un problema similar y apliqué su solución (en el método de establecimiento borro la colección de niños - this.children.clear () - y agregué los nuevos niños - this.children.addAll (niños)). Este cambio no solucionó mi problema. Todavía recibo la excepción "A collection with cascade =" all-delete-huérfano "ya no estaba referenciada por la instancia de la entidad propietaria". ¿Tienes idea de por qué? ¡Muchas gracias!
ovdsrn
@ovdsrn Lo siento, esta no es mi respuesta, acabo de resolver el formato de la respuesta, el autor original (kmmanu) podría ayudarlo (o puede que desee comenzar una nueva pregunta si su escenario es diferente a la pregunta original aquí) Buena suerte
Skuld
1
Esto funciona bien, pero no tan bien cuando los elementos secundarios están contenidos en un componente de hibernación anidado y el componente se convierte en nulo en su entidad. Entonces obtienes el mismo error. Esto se debe a que el propietario de los elementos secundarios es la entidad raíz y no el componente que se convierte en nulo ... De este modo, un componente nunca puede volverse nulo, sino que debe dar como resultado un método de reenvío a un destructor () en el niño ... Al menos, no conozco una solución mejor ... Es una especie de colección clear () construcción pero luego en un componente a través de destroy () ...
edbras
Consulte también esta publicación para obtener más detalles de lo anterior: stackoverflow.com/questions/4770262/…
edbras
77
use this.sonEntities.retainAll (aSet) sobre sonEntities.clear () porque si aSet == this.sonEntities (es decir, el mismo objeto) ¡borrará el conjunto antes de agregarlo!
Martin
33

Cuando leí en varios lugares que a Hibernate no le gustaba que asignaras a una colección, supuse que lo más seguro sería, por supuesto, hacerla final así:

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

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

Sin embargo, esto no funciona, y obtiene el temido error "ya no se hace referencia", que en realidad es bastante engañoso en este caso.

Resulta que hibernate llama a su método setRoles Y quiere que su clase de colección especial esté instalada aquí, y no aceptará su clase de colección. Esto me dejó perplejo por MUCHO tiempo, a pesar de leer todas las advertencias sobre no asignar a su colección en su método establecido.

Entonces cambié a esto:

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);
  }
}
}

Para que en la primera llamada, hibernate instale su clase especial, y en las llamadas posteriores puede usar el método usted mismo sin destruir todo. Si desea utilizar su clase como un bean, probablemente necesite un setter que funcione, y esto al menos parece funcionar.

xpusostomos
fuente
2
¿Por qué estás llamando aerveAll ()? ¿Por qué no clear () seguido de addAll ()?
edbras
1
"¿Por qué estás llamando aerveAll ()? ¿Por qué no borrar () seguido de addAll ()?" Buena pregunta, creo que estaba trabajando bajo el supuesto de que sería menos probable que hibernate vea esto como una actualización de la base de datos si no elimina los elementos antes de agregarlos nuevamente. Pero sospecho que de todos modos no funciona de esa manera.
xpusostomos
2
Debería utilizar retener todo () sobre clear () de lo contrario, puede eliminar los roles si pasa el objeto establecido idéntico. Por ejemplo: user.setRoles (user.getRoles ()) == user.roles.clear ()
Martin
2
Cuando configura orphanRemoval = true y crea un registro donde esta colección es nula, también obtiene este error. Entonces: a tiene oneToMany b con orphanremoval = true. Cuando crea A donde B = nulo, se desencadena este problema. Su solución para inicializar y hacerla final parece la mejor.
Lawrence
¡Gracias! Estaba inicializando mi Lista como List<String> list = new ArrayList<>();. Cambiarlo para List<String> list = null;solucionar el problema :)
Radical
19

En realidad, mi problema era sobre iguales y hashcode de mis entidades. Un código heredado puede traer muchos problemas, nunca olvides revisarlo. Todo lo que hice fue mantener la estrategia de borrar huérfanos y corregir los iguales y el código hash.

axcdnt
fuente
99
por favor, ¿un ejemplo de un método bien hecho igual y hashCode? porque tengo muchos problemas: o no puedo actualizar mi conjunto u obtengo un error de StackOverflow. Abrí una pregunta aquí: stackoverflow.com/questions/24737145/… . gracias
SaganTheBest
11

Yo tenía el mismo error. El problema para mí era que, después de guardar la entidad, la colección asignada seguía siendo nula y cuando intentaba actualizar la entidad se generaba la excepción. Lo que me ayudó: Guardar la entidad, luego hacer una actualización (la colección ya no es nula) y luego realizar la actualización. Tal vez inicializar la colección con el nuevo ArrayList () o algo podría ayudar también.

Konsumierer
fuente
Enviar nuevo ArrayList en lugar de nulo, funcionó para mí. Gracias
rpajaziti
4

TIENE TIPO DE RELACIÓN:


No intentes crear una instancia de la colección cuando esté declarada hasMany, solo agrega y elimina objetos.

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

TIPO DE RELACIÓN DE USO:


Pero la colección podría ser nula solo cuando se declara como una propiedad (relación de uso) y no se inicializa en la declaración.

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

Utilicé el enfoque @ user2709454 con una pequeña mejora.

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
fuente
3

Tuve este problema al intentar usar TreeSet. Inicialicé oneToManycon TreeSetqué obras

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

Pero, esto traerá el error descrito en lo questionanterior. Entonces parece que es hibernatecompatible SortedSety si uno solo cambia la línea de arriba a

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

funciona como magia :) más información sobre hibernate SortedSetpuede estar aquí

roble
fuente
Tal vez, es mejor usar Collections.emptySet () para usar una lista vacía singleton
ryzhman
3

La única vez que recibo este error es cuando intento pasar NULL al setter para la colección. Para evitar esto, mis setters se ven así:

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

Me encontré con esto al actualizar una entidad con una solicitud de publicación JSON. El error ocurrió cuando actualicé la entidad sin datos sobre los hijos, incluso cuando no había ninguno. Agregando

"children": [],

El cuerpo de la solicitud resolvió el problema.

co ting
fuente
1

Otra causa puede estar usando lombok.

@Builder- hace que se guarde Collections.emptyList()incluso si dices.myCollection(new ArrayList());

@Singular- ignora los valores predeterminados de nivel de clase y deja el campo nullincluso si el campo de clase se declaró comomyCollection = new ArrayList()

Mis 2 centavos, solo pasé 2 horas con lo mismo :)

Jan Zyka
fuente
1

Estoy usando Spring Boot y tuve este problema con una colección, a pesar de no sobrescribirlo directamente, porque estoy declarando un campo adicional para la misma colección con un serializador y deserializador personalizado para proporcionar una representación más amigable de la interfaz. datos:

  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;
  }

Parece que aunque no estoy sobrescribiendo la colección yo mismo , la deserialización lo hace bajo el capó, desencadenando este problema de todos modos. La solución fue cambiar el setter asociado con el deserializador para que borre la lista y agregue todo, en lugar de sobrescribirlo:

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

Tuve el mismo problema, pero fue cuando el conjunto era nulo. Solo en la colección Establecer en el trabajo de búsqueda de lista. Puede intentar la anotación de hibernación @LazyCollection (LazyCollectionOption.FALSE) en lugar de la anotación JPA fetch = FetchType.EAGER.

Mi solución: esta es mi configuración y funciona 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
fuente
1
@OneToMany(mappedBy = 'parent', cascade= CascadeType.ALL, orphanRemoval = true)
List<Child> children = new ArrayList<>();

Experimenté el mismo error cuando estaba agregando objetos secundarios a la lista existente de objetos secundarios.

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

Lo que resolvió mi problema está cambiando a:

child = childService.saveOrUpdate(child);

Ahora el niño también revive con otros detalles y funcionó bien.

Neha Gupta
fuente
0

Agregando mi tonta respuesta. Estamos usando Spring Data Rest. Esta fue nuestra relación bastante estándar. El patrón se usó en otro lugar.

//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

Con la relación que creamos, siempre se pretendía que los niños se agregaran a través de su propio repositorio. Todavía no había agregado el repositorio. La prueba de integración que tuvimos fue a través de un ciclo de vida completo de la entidad a través de llamadas REST para que las transacciones se cerraran entre las solicitudes. Ningún repositorio para el niño significaba que el json tenía a los niños como parte de la estructura principal en lugar de dentro _embedded. Las actualizaciones a los padres causarían problemas.

Snekse
fuente
0

La siguiente solución funcionó para mí

//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
fuente
0

En lugar de asignar una nueva colección

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

Reemplace todos los elementos con

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

ten cuidado con

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

Este método también rompe la hibernación.

Diógenes Ricardo
fuente
0

¡El mío fue completamente diferente con Spring Boot! Para mí no se debió a la configuración de la propiedad de colección.

¡En mis pruebas estaba tratando de crear una entidad y estaba recibiendo este error para otra colección que no se usó!

Después de tanto intentarlo, simplemente agregué un @Transactionalmétodo de prueba y lo resolvió. Sin embargo, no la razón.

madz
fuente
0

Esto está en contraste con las respuestas anteriores, tuve exactamente el mismo error: "Una colección con cascade =" all-delete-huérfano "ya no estaba referenciada ..." cuando mi función de configuración se veía así:

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

Y luego desapareció cuando lo cambié a la versión simple:

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

(versiones de hibernación: probé 5.4.10 y 4.3.11. Pasé varios días probando todo tipo de soluciones antes de volver a la tarea simple en el setter. Ahora estoy confundido sobre por qué esto es así).

ligett
fuente