Hibernacja - do kolekcji z cascade = „all-delete-orphan” nie odwoływała się już instancja będąca właścicielem obiektu

225

Występuje następujący problem podczas próby aktualizacji mojej jednostki:

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

Mam podmiot nadrzędny i ma on Set<...>niektóre podmioty podrzędne. Kiedy próbuję go zaktualizować, otrzymuję wszystkie odniesienia do tych kolekcji i ustawiam je.

Poniższy kod reprezentuje moje mapowanie:

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

Próbowałem wyczyścić tylko zestaw <..>, zgodnie z tym: Jak „możliwe” rozwiązać problem, ale to nie zadziałało.

Jeśli masz jakieś pomysły, daj mi znać.

Dzięki!

axcdnt
źródło
1
@ mel3kings, podany link nie jest już aktywny.
Opal
spróbuj użyć zmiennych zbiorów podczas usuwania elementów. Na przykład nie używaj, something.manyother.remove(other)jeśli manyotherjest List<T>. Zrób wiele innych Zmiennych, polub ArrayList<T>i użyjorphanDelete = true
nurettin

Odpowiedzi:

220

Sprawdź wszystkie miejsca, w których przypisujesz coś do sonEntities. Odnośnik, do którego się odnosisz, wyraźnie wskazuje na utworzenie nowego zestawu HashSet, ale możesz napotkać ten błąd przy każdym ponownym przypisaniu zestawu. Na przykład:

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

Zwykle chcesz tylko „nowy” zestaw raz w konstruktorze. Za każdym razem, gdy chcesz dodać lub usunąć coś z listy, musisz zmodyfikować zawartość listy zamiast przypisywać nową listę.

Aby dodać dzieci:

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

Aby usunąć dzieci:

public void removeChild(SonEntity aSon)
{
    this.sonEntities.remove(aSon);
}
mózg
źródło
8
W rzeczywistości mój problem dotyczył równości i kodu skrótu moich bytów. Stary kod może przynieść wiele problemów, nigdy nie zapomnij go sprawdzić. Wszystko, co zrobiłem, to po prostu utrzymywać strategię usuwania-osieroconych i poprawiać równość i kod skrótu.
axcdnt,
6
Cieszę się, że rozwiązałeś swój problem. equals i hashcode ugryzły mnie kilka razy Hibernacją. Zamiast aktualizować tytuł pytania za pomocą „[Rozwiązane]”, powinieneś opublikować swoją odpowiedź, a następnie oznaczyć ją jako odpowiedź zaakceptowaną.
brainimus
Dzięki, wpadłem na coś podobnego i okazało się, że mój seter był kompletny.
Siddhartha
tak, nawet na pustych listach pojawia się ten błąd. nie spodziewałbym się tego, ponieważ nie ma sierot (lista była pusta).
tibi
4
Zwykle chcesz tylko „nowy” zestaw raz w konstruktorze. Za każdym razem, gdy chcesz dodać lub usunąć coś z listy, musisz zmodyfikować zawartość listy zamiast przypisywać nową listę. Najbardziej chochlik
Nikhil Sahu,
109

Metoda:

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

działa, jeśli parentEntityjest odłączony i ponownie, jeśli go zaktualizujemy.
Ale jeśli jednostka nie jest oddzielona od kontekstu (tj. Operacje wyszukiwania i aktualizacji są w tej samej transakcji), działa poniższa metoda.

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
źródło
1
@ Skuld Mam podobny problem i zastosowałem twoje rozwiązanie (w metodzie setera wyczyściłem kolekcję dzieci - this.children.clear () - i dodałem nowe dzieci - this.children.addAll (dzieci)). Ta zmiana nie rozwiązała mojego problemu. Nadal otrzymuję komunikat „Kolekcja z cascade =„ all-delete-orphan ”nie była już przywoływana przez wyjątek instancji właściciela”. Masz pomysł dlaczego? Dziękuję Ci bardzo!
ovdsrn
@ovdsrn Przepraszamy, to nie moja odpowiedź, właśnie uporządkowałem formatowanie odpowiedzi, oryginalny autor (kmmanu) może być w stanie Ci pomóc (lub alternatywnie możesz rozpocząć nowe pytanie, jeśli twój scenariusz jest inny niż pierwotne pytanie zadane tutaj) Powodzenia
Skuld
1
Działa to dobrze, ale nie tak dobrze, gdy dzieci znajdują się w zagnieżdżonym komponencie hibernacji, a komponent jest pusty w swojej jednostce. Otrzymujesz ten sam błąd. Wynika to z faktu, że właścicielem dzieci jest Entity root, a nie komponent, który ma wartość null ... Taki komponent nigdy nie może stać się null, ale raczej powinien skutkować przekazaniem do metody destroy () w dziecko ... Przynajmniej nie znam lepszego rozwiązania ... To rodzaj kolekcji przejrzystej () konstrukcji, ale potem na komponencie poprzez destroy () ...
edbras
Zobacz także ten post, aby uzyskać więcej informacji na temat powyżej: stackoverflow.com/questions/4770262/…
edbras
7
użyj this.sonEntities.retainAll (aSet) nad sonEntities.clear (), ponieważ jeśli aSet == this.sonEntities (tj. ten sam obiekt) wyczyścisz zestaw zanim go dodasz!
Martin
33

Kiedy czytałem w różnych miejscach, których hibernacja nie lubiła przypisywać do kolekcji, założyłem, że najbezpieczniejszym rozwiązaniem byłoby oczywiście ostateczne:

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

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

Jednak to nie działa i pojawia się przerażający błąd „nie ma już odniesienia”, co w tym przypadku jest dość mylące.

Okazuje się, że hibernacja wywołuje metodę setRoles ORAZ chce zainstalować tutaj swoją specjalną klasę kolekcji i nie zaakceptuje twojej klasy kolekcji. Długo mnie to zaskoczyło, pomimo przeczytania wszystkich ostrzeżeń o nieprzypisywaniu do Twojej kolekcji w ustawionej metodzie.

Więc zmieniłem na:

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

Tak więc przy pierwszym wywołaniu hibernacja instaluje swoją specjalną klasę, a przy kolejnych wywołaniach możesz użyć tej metody samodzielnie, nie niszcząc wszystkiego. Jeśli chcesz użyć swojej klasy jako fasoli, prawdopodobnie potrzebujesz działającego setera, a przynajmniej to wydaje się działać.

Xpusostomos
źródło
2
Dlaczego wywołujesz retainAll ()? Dlaczego nie wyczyścić (), a następnie addAll ()?
edbras
1
„Dlaczego wywołujesz retainAll ()? Dlaczego nie wyczyścić (), a następnie addAll ()?” Dobre pytanie, myślę, że pracowałem przy założeniu, że hibernacja będzie mniej postrzegać to jako aktualizację bazy danych, jeśli nie usuniesz elementów przed ich ponownym dodaniem. Ale podejrzewam, że i tak to nie działa.
xpusostomos
2
Powinieneś użyć retainAll () zamiast clear (), w przeciwnym razie możesz wymazać role, jeśli zdarzy ci się przekazać ten sam zestaw obiektów. Na przykład: user.setRoles (user.getRoles ()) == user.roles.clear ()
Martin
2
Gdy ustawisz orphanRemoval = true i utworzysz rekord, w którym ta kolekcja ma wartość NULL, również pojawi się ten błąd. A zatem: a ma oneToMany bz orphanremoval = true. Podczas tworzenia A, gdzie B = null, ten problem jest wyzwalany. Twoje rozwiązanie do zainicjowania i uczynienia go ostatecznym wydaje się najlepsze.
Lawrence,
Dziękuję Ci! Inicjowałem moją listę jakoList<String> list = new ArrayList<>(); . Zmieniłem to, aby List<String> list = null;rozwiązać problem :)
Radykalny
19

W rzeczywistości mój problem dotyczył równości i kodu skrótu moich bytów. Stary kod może przynieść wiele problemów, nigdy nie zapomnij go sprawdzić. Wszystko, co zrobiłem, to po prostu utrzymywać strategię usuwania-osieroconych i poprawiać równość i kod skrótu.

axcdnt
źródło
9
proszę, przykład dobrze wykonanych metod równych i hashCode? ponieważ mam wiele problemów: lub nie mogę zaktualizować mojego zestawu lub pojawia się błąd StackOverflow. otworzyłem pytanie tutaj: stackoverflow.com/questions/24737145/… . dzięki
SaganTheBest
11

Miałem ten sam błąd. Problemem było dla mnie to, że po zapisaniu encji odwzorowana kolekcja nadal była pusta i podczas próby aktualizacji encji wyjątek został zgłoszony. Co mi pomogło: Zapisanie encji, następnie odświeżenie (kolekcja nie jest już pusta), a następnie wykonanie aktualizacji. Może zainicjowanie kolekcji za pomocą nowej ArrayList () lub coś innego może również pomóc.

Konsumierer
źródło
Wysyłanie nowej ArrayList zamiast null działało dla mnie. Dzięki
rpajaziti
4

MA TYP RELACJI:


Nie próbuj tworzyć instancji kolekcji, gdy jest zadeklarowana hasMany, po prostu dodawaj i usuwaj obiekty.

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

UŻYJ RODZAJU RELACJI:


Ale kolekcja może być pusta tylko wtedy, gdy zostanie zadeklarowana jako właściwość (relacja użycia) i nie zostanie zainicjowana w deklaracji.

class Parent {
    List<Child> childs = []
}
IgniteCoders
źródło
4

Zastosowałem podejście @ user2709454 z niewielką poprawą.

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
źródło
3

Miałem ten problem podczas próby użycia TreeSet. Zainicjowałem za oneToManypomocą TreeSetktórych działa

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

Spowoduje to jednak błąd opisany questionpowyżej. Wygląda więc na to, że hibernateobsługiwane, SortedSeta jeśli po prostu zmień powyższą linię na

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

działa jak magia :) Więcej informacji na temat hibernate SortedSetmożna znaleźć tutaj

dąb
źródło
Być może lepiej jest dla użytkownika Collections.emptySet () skorzystać z pustej listy singletonów
ryzhman
3

Ten błąd pojawia się tylko wtedy, gdy próbuję przekazać wartość NULL do elementu ustawiającego dla kolekcji. Aby temu zapobiec, moi seterzy wyglądają tak:

public void setSubmittedForms(Set<SubmittedFormEntity> submittedForms) {
    if(submittedForms == null) {
        this.submittedForms.clear();
    }
    else {
        this.submittedForms = submittedForms;
    }
}
Arlo Guthrie
źródło
3

Natrafiłem na to podczas aktualizacji encji za pomocą żądania postu JSON. Błąd wystąpił, gdy zaktualizowałem byt bez danych o dzieciach, nawet jeśli nie było żadnych. Dodawanie

"children": [],

do ciała żądania rozwiązało problem.

współpraca
źródło
1

Inną przyczyną może być używanie lombok.

@Builder- powoduje, że oszczędzasz, Collections.emptyList()nawet jeśli mówisz.myCollection(new ArrayList());

@Singular- ignoruje wartości domyślne na poziomie klasy i pozostawia pole, nullnawet jeśli pole klasy zostało zadeklarowane jakomyCollection = new ArrayList()

Moje 2 centy, spędziłem 2 godziny z tym samym :)

Jan Zyka
źródło
1

Korzystam z Spring Boot i miałem ten problem z kolekcją, mimo że nie nadpisałem go bezpośrednio, ponieważ deklaruję dodatkowe pole dla tej samej kolekcji za pomocą niestandardowego serializatora i deserializatora , aby zapewnić bardziej przyjazną dla interfejsu reprezentację dane:

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

Wydaje się, że mimo iż nie jestem nadpisywania kolekcji siebie The deserializacji robi to pod maską, wywołując ten problem tak samo. Rozwiązaniem było zmienić seter związany z deserializatorem, aby wyczyścił listę i dodał wszystko, zamiast go nadpisywać:

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

Miałem ten sam problem, ale było to, gdy zestaw był zerowy. Tylko w kolekcji Ustaw na liście prac znaleźć. Możesz spróbować hibernować adnotację @LazyCollection (LazyCollectionOption.FALSE) zamiast adnotacji JPA fetch = FetchType.EAGER.

Moje rozwiązanie: To jest moja konfiguracja i działa dobrze

@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
źródło
1
@OneToMany(mappedBy = 'parent', cascade= CascadeType.ALL, orphanRemoval = true)
List<Child> children = new ArrayList<>();

Ten sam błąd występował podczas dodawania obiektu potomnego do istniejącej listy obiektów potomnych.

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

Rozwiązaniem mojego problemu jest zmiana na:

child = childService.saveOrUpdate(child);

Teraz dziecko ożywa z innymi szczegółami i działało dobrze.

Neha Gupta
źródło
0

Dodanie mojej głupiej odpowiedzi. Używamy Spring Data Rest. To był nasz dość standardowy związek. Wzór został użyty gdzie indziej.

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

W związku, który stworzyliśmy, zawsze było zamierzone, aby dzieci były dodawane poprzez własne repozytorium. Nie dodałem jeszcze repozytorium. Test integracji, który przeprowadziliśmy, obejmował pełny cykl życia jednostki za pośrednictwem wywołań REST, aby transakcje były zamykane między żądaniami. Brak repo dla dziecka oznaczało, że Json miał dzieci jako część głównej struktury zamiast w _embedded. Uaktualnienia rodzica spowodowałyby wówczas problemy.

Snekse
źródło
0

Poniższe rozwiązanie działało dla mnie

//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
źródło
0

Zamiast przypisywać nową kolekcję

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

Zamień wszystkie elementy na

public void setChildren(Set<ChildEntity> children) {
    Collections.replaceAll(this.children,children);
}
Marcin Szymczak
źródło
0

Uważaj z

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

Ta metoda również przerywa hibernację.

Diógenes Ricardo
źródło
0

Mój był zupełnie inny z Spring Boot! Dla mnie nie było to spowodowane ustawieniem właściwości kolekcji.

W moich testach próbowałem utworzyć encję i otrzymywałem ten błąd dla kolejnej nieużywanej kolekcji!

Po tylu próbach właśnie dodałem @Transactionalmetodę testową i to rozwiązało. Nie ma jednak powodu.

madz
źródło
0

Jest to w przeciwieństwie do poprzednich odpowiedzi, miałem dokładnie ten sam błąd: „Kolekcja z cascade =„ all-delete-orphan ”nie była już przywoływana ....”, gdy moja funkcja settera wyglądała tak:

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

A potem zniknął, kiedy zmieniłem go na prostą wersję:

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

(wersje hibernacyjne - wypróbowałem zarówno wersje 5.4.10, jak i 4.3.11. Spędziłem kilka dni próbując różnego rodzaju rozwiązań, zanim wróciłem do prostego zadania w seterie. Teraz jestem zdezorientowany, dlaczego tak jest.)

ligett
źródło