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
Openjdk Availability for Windows Os
How to Create a Custom Exception Type in Java
What Is the Best/Simplest Way to Read in an Xml File in Java Application
How to Generate Javadoc HTML Files in Eclipse
How to Do an Integer.Parseint() for a Decimal Number
How to Write Our Own Iterator in Java
Calling Jmx Mbean Method from a Shell Script
Using Spring 3 Autowire in a Standalone Java Application
Correct Use of Flush() in JPA/Hibernate
Why Are the Level.Fine Logging Messages Not Showing
What Hashing Function Does Java Use to Implement Hashtable Class
Output an Image File from a Servlet
Difference Between @Onetomany and @Elementcollection
How to Test If My Font Is Rendered Correctly in PDF
Differencebetween an Ordered and a Sorted Collection
How to Specify Correctly Codebase and Archive in Java Applet