Hibernate - a Collection with Cascade="All-Delete-Orphan" Was No Longer Referenced by the Owning Entity Instance

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

Actually, my problem was about equals and hashcode of my entities. A legacy code can bring a lot of problems, never forget to check it out. All I've done was just keep delete-orphan strategy and correct equals and hashcode.

Hibernate:collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance

Most common problem is to overwrite your collection rather than add to it.

If you happend to do something like that :

post.dateActiveScheduleItems = list;

This may cause following problem. Rather than that try this.

post.dateActiveScheduleItems.addAll(list);

You should work with one instance of your collection, just clear it, add to it, never overwrite.

Hibernate Error. A collection with cascade=all-delete-orphan was no longer referenced by the owning entity instance. After second session.flush

You code is a little... peculiar, but is this line:

p.setC(dtoListOfCs, p);

overwriting Parent.children with a fresh collection?

Hibernate doesn't like it when you override a collection property of a managed entity with a fresh collection when the mapping is annotated with @OneToMany(orphanRemoval = true). You need to use the original list instead, adding/removing children, and clearing it, if necessary.

(Note that the Parent entity returned from session.merge() will already have an empty collection created by Hibernate, even if it was null before the merge. You're supposed to use that one)

A collection with cascade=all-delete-orphan was no longer referenced by the owning entity instance - Spring and Lombok

Fast solution (but not recommended)

The error of collection [...] no longer referenced arrises in your code beacuse the synchronization between both sides of the bidiretional mapping category-report was just partially done.

It's important to note that binding the category to the report and vice-versa is not done by Hibernate. We must do this ouserselves, in the code, in order to sync both sides of the relationship, otherwise we may break the Domain Model relationship consistency.

In your code you have done half of the synchronization (binding the category to the report):

existingReport.setCategory(category);

What is missing is the binding of the report to the category:

category.addReport(existingReport);

where the Category.addReport() may be like that:

public void addReport(Report r){
if (this.report == null){
this.report = new ArrayList<>();
}
this.report.add(r);
}

Recommended Solution - Best practice for synchronizing both sides of the mapping

The suggested code above works, but it is error prone as the programmer may forget to call one of the lines when updating the relationship.

A better approach is to encapsulate that sychronization logic in a method in the owning side of the relationship. And that side is the Category as stated here: mappedBy = "category".

So what we do is to encapsulate in the Category.addReport(...) all the logic of cross-reference between Category and Report.

Considering the above version of addReport() method, what is missing is adding r.setCategory(this).

public class Category {

public void addReport(Report r){
if (this.reports == null){
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}
}

Now, in the updateReport() it is enough to call the addReport() and the commented line bellow can be deleted:

//existingReport.setCategory(category); //That line can be removed
category.addReport(existingReport);

It is a good practice including in Category a removeReport() method as well:

public void removeReport(Report r){
if (this.reports != null){
r.setCategory = null;
this.reports.remove(r);
}
}

That is the code of Category.java after the two methods were added:

public class Category {

@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;


//Code ommited for brevity


public void addReport(Report r){
if (this.reports == null){
this.reports = new ArrayList<>();
}
r.setCategory(this);
this.reports.add(r);
}

public void removeReport(Report r){
if (this.reports != null){
r.setCategory = null;
this.reports.remove(r);
}
}
}

And the code for updating a report category now is this:

public ReportUpdateDto updateReport(UUID id, ReportUpdateDto reportUpdateDto) {

if (reportRepository.findById(id).isPresent()) {

Report existingReport = reportRepository.findById(id).get();
existingReport.setReportTitle(reportUpdateDto.getTitle());

Category existingCategory = categoryRepository.findById(reportUpdateDto.getCategory().getId()).get();
existingCategory.addReport(existingReport);
reportRepository.save(existingReport);

return new ReportUpdateDto(existingReport.getId(),
existingReport.getReportTitle(), existingReport.getCategory());
} else {
return null;
}
}

A good resource to see a practical example of synchronization in bidirectional associations: https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/

Lombok and Hibernate - not the best of the combinations

Though we can not blame Lombok for the error described in your question, many problems may arrise when using Lombok alongside with Hibernate:

Properties being loaded even if marked for lazy loading...

When generating hashcode(), equals() or toString() using Lombok, the getters of fields marked as lazy are very likelly to be called. So the programmer's initial intention of postponing some properties loading will no be respected as they will be retrieved from the database when one of hascode(), equals() or toString() is invoked.

In the best case scenario, if a session is open, this will cause additional queries and slow down your application.

In the worst case scenarios, when no session is available, a LazyInitializationException will be thrown.

Lombok's hashcode()/equals() affecting the bevahior of collections

Hibernate uses hascode() and equals() logic to check if a object is order to avoid inserting the same object twice. The same applies to removing from a list.

The way Lombok generates the methods hashcode() and equals() may affect hibernate and create inconsistent properties (especially Collections).

See this article for more info on this subject: https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/

Lombok/Hibernate integration in a nutshell

Don't use Lombok for entity classes. Lombok annotations you need to avoid are @Data, @ToString, and @EqualsAndHashCode.

Off-topic - Beware of delete-orphan

In Category, the @OneToMany mapping is defined with orphanRemoval=true as bellow:

@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<Report> reports;

The orphanRemoval=true means that when deleting a category, all the reports in that category will be deleted as well.

It is important to assess if that is the desired behavior in your application.

See an example of the SQLs hibernate will execute when calling categoryRepository.delete(category):

    //Retrieving all the reports associated to the category
select
report0_.category_id as category3_1_0_,
report0_.id as id1_1_0_,
report0_.id as id1_1_1_,
report0_.category_id as category3_1_1_,
report0_.report_title as report_t2_1_1_
from
report report0_
where
report0_.category_id=?
    //Deleting all the report associated to the category (retrieved in previous select)
delete from
report
where
id=?
    //Deleting the category
delete from
category
where
id=?

Exception: A collection with cascade=all-delete-orphan was no longer referenced by the owning entity instance

A CascadeType.ALL is used when one wants that the changes in parents propagate to its children not the other way round...

So, it is a common pattern using cascade = {CascadeType.ALL} in @OneToMany relationships but very unusual to use it on @ManyToOne relationships.

At a first glance I would guess that the CascadeType.ALL in the @ManyToOne reportingTo relationship should not be there in first place...

In that other question you may find usefull information about @ManyToOne and cascading:
What is the meaning of the CascadeType.ALL for a @ManyToOne JPA association



Related Topics



Leave a reply



Submit