Hibernate: lazy collections and session.merge - java

What I have:
I need to update my entity. I use Hibernatee conversation, that means that we already have entity in the session cache.
public void handle(Request request, Session session) {
MyEntity updatedEntity = request.getEntity();
session.merge(updatedEntity); //rewrite all lazy collections
}
So I send object to client, client full up the object and send it back for update.
What the problem:
Lazy collections aren't sent to the client. As a result, if lazy collection hasn't been empty, than it will be overriden in session.merge(updatedEntity) string
It happens because client knows nothing about element in those collections
Question:
How two merge entity in correct way? Means without rewriting lazy collections.
EDIT:(how I work with my collections)
public class MyEntity {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "entity_id")
private List<AnotherEntity> anotherEntities;
public void setAnotherEntities(List<AnotherEntity> anotherEntities) {
// we must work with the same instance of list (again, because of orphanRemoval)
this.anotherEntities.clear();
this.anotherEntities.addAll(anotherEntities);
}
}

I think that CascadeType.ALL is the problem
You may use instead
cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DELETE})
You may also add to this set any other cascade option you need.

Related

Is it possible to change cascade type dynamically in JPA/Hibernate?

Consider the following code:
#Entity
public class User {
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private List<UserRole> roles;
}
According to the code above User.roles will be loaded lazily. However, we can change this behavior using fetchgraph, something like this:
EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
graph.addSubgraph("roles");
typedQuery.setHint("javax.persistence.fetchgraph", graph);
List<User> entities = typedQuery.getResultList();//roles will be eagerly loaded
Is is possible to make JPA provider/Hibernate create and update User with roles field when #OneToMany.cascade = null? By other words, I want to add cascade = CascadeType.ALL dynamically to have a full and dynamic control over entity tree for all create/update/delete operations.

Spring Data REST + JPA remove from OneToMany collection [not owner side]

Currently we have an issue (a well known one) with Spring Data JPA + Spring Data REST (Hibernate as JPA implementation) when trying to update the collection (relation) which is a not the owning side.
The mapping is the following:
#Entity(name = Product.NAME)
public class Product {
...
#OneToMany(mappedBy = "baseProduct", fetch = FetchType.LAZY, targetEntity = Variant.class)
List<Variant> getVariants() {
...
and on the other variant side:
#Entity(name = Variant.NAME)
public class Variant extends Product {
...
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Product.class)
#JoinColumn(name = "baseproduct_id", referencedColumnName = "id")
Product getBaseProduct() {
...
}
all is good on the Java side if you use Spring Data JPA only, however if you want to update the "product" by updating its collection of variants and send PATCH request to https://localhost:8112/storefront/rest/product/21394435410197232 containing the payload of the new collection only (having 2 out of the 3 items):
{"variants":["22801810293768080","22801810293768096"]}
I get no exceptions or anything but since the owning side is the other side nothing is persisted and I got the old 3 items again.
I know that I can fix this by setting
#JoinColumn(name = "baseproduct_id", referencedColumnName = "id")
on both sides and not use mappedBy anywhere, however I have heard there is a performance implication which I am not sure how big it is (we got 100+ entities having #OneToMany) and I wonder is there better workaround via #PreUpdate listener or something ?
You have to synchronize both sides of the bidirectional association, and also add on orphanRemoval and Cascade.
So, your mapping becomes:
#OneToMany(
mappedBy = "baseProduct",
fetch = FetchType.LAZY,
targetEntity = Variant.class
cascade = CascadeType.ALL,
orphanRemoval = true)
List<Variant> getVariants() {
And the two add/remove methods:
public void addVariant(Variant variant) {
getVariants().add(variant);
variant.setBaseProuct(this);
}
public void removeVariant(Variant variant) {
variant.setBaseProuct(null);
this.getVariants().remove(variant);
}
You need to implement equals and hashCode methods in the Variant child entity for the add and remove methods to work effectively.

JPA - Eager - Lazy best practice

JBoss EAP 6
Hibernate 4
I have a J2EE application with a web browser client. ( Apache click )
Both the internal business logic and the client use the same entity objects.
I would like to have all relations in the entities set to lazy loading. This way I have good performance.
But when using the entities in the client ( that is the server side code of apache click ) I would need a lot of the relations to be eager loaded. The client code is accessing the back-end through a session bean.
So I have a couple of ways I can solve this:
Create 2 of each JPA entities, one with eager loading and one with lazy loading. And then use the one with eager loading in the client, and the one with lazy loading in the server. Most of the server logic will be in a transaction, so lazy loading is fine here.
Make all relations lazy loading. When accessing the entities from the client, make sure there is a transaction. ( #TransactionAttribute(TransactionAttributeType.REQUIRED) )
and code the access to the necessary fields so they are accessible after session bean call.
But that means that I have to start a transaction when that is not required, i.e. if I am only getting some objects. And I have to maintain more code. And I have to know exactly what relations the client needs.
Create an inheritance hierarchy, where I have a super entity, and then 2 child, one with objects relations lazy loaded, and one with only values, no objects. i.e. :
Super
#MappedSuperclass
public class SuperOrder {
#Id
#Column(name = "id")
#GeneratedValue(.....)
private Long id;
#Column(name = "invoice", length = 100)
private String invoice;
Child 1
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "testorder")
#SequenceGenerator(....)
public class Order extends SuperOrder {
#ManyToOne(targetEntity = PrintCustomerEnt.class, fetch = FetchType.EAGER, optional = true)
#JoinColumn(name = "print_customer_id", nullable = true)
#ForeignKey(name = "fk_print_customer")
#Valid
private PrintCustomerEnt printCustomer;
public PrintCustomerEnt getPrintCustomer() {
return printCustomer;
}
public void setPrintCustomer(final PrintCustomerEnt printCustomer) {
this.printCustomer = printCustomer;
}
}
Child 2
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Table(name = "testorder")
#SequenceGenerator(...)
public class LazyOrder extends SuperOrder {
#Transient
private String printCustomerName;
#Column(name = "print_customer_id", nullable = true)
private Long printCustomerId;
What is the best practice... or is there something other good way to do this.
Basically the problem is I want to use the same entities in different scenarios. Sometimes I need eager loading, and sometimes I need lazy loading.
I suggest that you create just one JPA entity with lazy relationships, and when you need to load eagerly some of them create a Service that uses JPQL(HQL) to do some FETCH trick. The idea is one JPA entity and many services.
I've been programing in JPA 2 for some a while now, and I can say there are couple of now written rules that I almost always apply:
Use LAZY Inicialization on all your OneToMany, ManyToMany Relations
Use EAGER Inicalization on all your OneToOne, ManyToOne Relations
This rules apply on 99% of my projects. I think these are best practices due to my personal experience and some research I've been doing.
Note: I must say I do not use JOIN FETCH on Lazy Inicialization, instead I write a Prefetch Method. Example:
#Entity
class Entity{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child1> collection1;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child2> collection2; }
And then we have the Controller:
class EntityController{
public Entity findCompraFolioFull(Integer id) {
EntityManager em = getEntityManager();
try {
Entity entity = em.find(Entity.class, id);
//Initialize Collections inside Transaccion, this prevents
//LazyInizialization No Proxy Exception later in code when calling
//hollow collections
cp.getCollection().size();
cp.getCollection().size();
return cp;
} finally {
em.close();
}
}
}
I don't recomend FETCH JOIN
public Entity findEntityByJoinFetch(Integer id) {
EntityManager em = getEntityManager();
try {
TypedQuery<Entity> tq = em.createQuery(
"SELECT e FROM Entity e\n"
+ "LEFT JOIN FETCH e.collection1\n"
+ "LEFT JOIN FETCH e.collection2\n"
+ "WHERE e.id = :id", Entity.class);
tq.setParameter("id", id);
return tq.getSingleResult();
} finally {
em.close();
}
}
Reasons I don't recomend Fetch Join Appoach:
If your collections are java.util.List type then this getSingleResult() will fail in hibernate due to lack of capacity to fetch MultipleBags without indexing notations on your OneToMany Relation.
You can always change the type of your collections to java.util.set in order to multiple bags to be fetched but this brings new kind of situations to deal with, Sets aren't ordered and HashCode() method won't work correctly so you'll have to #Override it inside Children Classes, and if you are using JAVAFX TableView to bind model to Items you won't be able to bind collections Set Type to Item Property of TableView, not directly at least.

NullPointerException when accessing lazy loaded collection within DAO method

I'm having a strange issue with Spring and Hibernate when running multiple threads in an application. I'm using spring 3.2.0.RELEASE and hibernate 4.1.12.Final. The issue is that for some objects, when they are retrieved from the db, the retrieval succeeds, but all mapped collections are not being set. Here's an example of my repo:
#Repository("fooRepository")
public class FooRepository {
private static final Logger log = Logger.getLogger(FooRepository.class);
#Autowired
private SessionFactory sessionFactory;
#Override
#Transactional
public Foo retrieve(final Long id) {
Foo instance = (Foo) sessionFactory.getCurrentSession().get(Foo.class, id);
for (CollectionMember member : instance.getCollectionMembers()) {
log.debug(member.getId());
}
return instance;
}
For some objects, this method always throws the following error when trying to access the CollectionMember list:
Caused by: java.lang.NullPointerException
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:501)
The problem is that the session.get() call creates PersistentBag objects for all lazy loaded collections but never sets the content. This only occurs when multithreading is enabled. Can anybody explain why this is happening?
EDIT:
Here's the relevant bit of the foo class:
#Entity
#Table(name = "FOO")
#XmlRootElement(name = "foo")
public class Foo {
#EmbeddedId
private FooPK id;
#OneToMany
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumns({
#JoinColumn(name = "COLLECTION_ID", referencedColumnName = "COLLECTION_ID"),
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")})
private List<CollectionMember> collectionMembers = new ArrayList<CollectionMember>();
And the CollectionMember class:
#Entity
#Table(name = "COLLECTION_MEMBER")
#XmlRootElement(name = "collectionMember")
public class CollectionMember {
#EmbeddedId
private CollectionMemberPK primaryKey;
#ManyToOne
#JoinColumn(name = "COLL_CODE")
private CollectionCode collectionCode;
I think you should use third join table like and use Criteria for loading collection.
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "table", joinColumns = {#JoinColumn(name = "COLLECTION_ID", nullable = false)}, inverseJoinColumns = { #JoinColumn(name = "FOO_ID", nullable = true) })
private List<CollectionMember> collectionMembers = new ArrayList<CollectionMember>();
#ManyToOne(mappedBy="collectionMembers")
private CollectionCode collectionCode;
Problem is #NotFound(action = NotFoundAction.IGNORE)
This will create a returned object null instead of an empty collection in case no collection records are found. Remove and test it
Where does the multithreading come into play? How are ensuring that a single thread always uses the same hibernate session?
The way I understand hibernate and lazy loading, when you traverse the collection Hibernate will look in the CurrentSession. In a multithreaded environment that session may already have been used by another a thread. I'm not sure if the #Transactional is enough to preserve your same Hibernate Session.
There may be a Hibernate configuration for this.
Try the following.
Foo instance = (Foo) sessionFactory.getCurrentSession().get(Foo.class, id);
Hibernate.initialize(instance.getCollectionMembers())
for (CollectionMember member : instance.getCollectionMembers()) {
log.debug(member.getId());
}
return instance;
Hibernate marks all *ToMany collection mappings as lazy by default, unless stated otherwise. It's impossible for one thread to load object and not being able to read associated lazy collection inside a one #Transactional method.
What is multi-threading in your case? Do you spawn threads manually, or is it web application? How do you set session bounds?
BTW, if you really want to solve the problem, I'd advise you to post the stacktrace.. it may take just few seconds to replace you commercial package names, in case it's concern.
When using hibernate in spring environment, it is not recommended to obtain session directly via session factory, is it may lead to session leakage.
Try to replace with the following pattern:
getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Foo instance = (Foo) session.get(Foo.class, id);
//whatever logic goes here
return instance;
}
});

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

I'm having the following issue when trying to update my entity:
"A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance".
I have a parent entity and it has a Set<...> of some children entities. When I try to update it, I get all the references to be set to this collections and set it.
The following code represents my mapping:
#OneToMany(mappedBy = "parentEntity", fetch = FetchType.EAGER)
#Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
public Set<ChildEntity> getChildren() {
return this.children;
}
I've tried to clean the Set<..> only, according to this: How to "possible" solve the problem but it didn't work.
If you have any ideas, please let me know.
Thanks!
Check all of the places where you are assigning something to sonEntities. The link you referenced distinctly points out creating a new HashSet but you can have this error anytime you reassign the set. For example:
public void setChildren(Set<SonEntity> aSet)
{
this.sonEntities = aSet; //This will override the set that Hibernate is tracking.
}
Usually you want to only "new" the set once in a constructor. Any time you want to add or delete something to the list you have to modify the contents of the list instead of assigning a new list.
To add children:
public void addChild(SonEntity aSon)
{
this.sonEntities.add(aSon);
}
To remove children:
public void removeChild(SonEntity aSon)
{
this.sonEntities.remove(aSon);
}
The method:
public void setChildren(Set<SonEntity> aSet) {
this.sonEntities = aSet;
}
works if the parentEntity is detached and again if we update it.
But if the entity is not detached from per context, (i.e. find and update operations are in the same transaction) the below method works.
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);
}
}
When I read in various places that hibernate didn't like you to assign to a collection, I assumed that the safest thing to do would obviously be to make it final like this:
class User {
private final Set<Role> roles = new HashSet<>();
public void setRoles(Set<Role> roles) {
this.roles.retainAll(roles);
this.roles.addAll(roles);
}
}
However, this doesn't work, and you get the dreaded "no longer referenced" error, which is actually quite misleading in this case.
It turns out that hibernate calls your setRoles method AND it wants its special collection class installed here, and won't accept your collection class. This had me stumped for a LONG time, despite reading all the warnings about not assigning to your collection in your set method.
So I changed to this:
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);
}
}
}
So that on the first call, hibernate installs its special class, and on subsequent calls you can use the method yourself without wrecking everything. If you want to use your class as a bean, you probably need a working setter, and this at least seems to work.
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.
I fixed by doing this :
1. clear existing children list so that they are removed from database
parent.getChildren().clear();
2. add the new children list created above to the existing list
parent.getChildren().addAll(children);
Hope this post will help you to resolve the error
I had the same error. The problem for me was, that after saving the entity the mapped collection was still null and when trying to update the entity the exception was thrown. What helped for me: Saving the entity, then make a refresh (collection is no longer null) and then perform the update. Maybe initializing the collection with new ArrayList() or something might help as well.
I ran into this when updating an entity with a JSON post request.
The error occurred when I updated the entity without data about the children, even when there were none.
Adding
"children": [],
to the request body solved the problem.
I used #user2709454 approach with small improvement.
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);
}
}
}
}
It might be caused by hibernate-enhance-maven-plugin. When I enabled enableLazyInitialization property this exception started on happening on my lazy collection. I'm using hibernate 5.2.17.Final.
Note this two hibernate issues:
https://hibernate.atlassian.net/browse/HHH-10708
https://hibernate.atlassian.net/browse/HHH-11459
All those answers didnt help me, BUT I found another solution.
I had an Entity A containing a List of Entity B. Entity B contained a List of Entity C.
I was trying to update Entity A and B. It worked. But when updating Entity C, I got the mentioned error. In entity B I had an annotation like this:
#OneToMany(mappedBy = "entity_b", cascade = [CascadeType.ALL] , orphanRemoval = true)
var c: List<EntityC>?,
I simply removed orphanRemoval and the update worked.
HAS RELATION TYPE:
Don't try to instantiate the collection when it's declared in hasMany, just add and remove objects.
class Parent {
static hasMany = [childs:Child]
}
USE RELATION TYPE:
But the collection could be null only when is declared as a property (use relation) and is not initialized in declaration.
class Parent {
List<Child> childs = []
}
The only time I get this error is when I try to pass NULL into the setter for the collection. To prevent this, my setters look like this:
public void setSubmittedForms(Set<SubmittedFormEntity> submittedForms) {
if(submittedForms == null) {
this.submittedForms.clear();
}
else {
this.submittedForms = submittedForms;
}
}
I had this problem when trying to use TreeSet. I did initialize oneToMany with TreeSet which works
#OneToMany(mappedBy = "question", fetch = FetchType.EAGER, cascade = { CascadeType.ALL }, orphanRemoval=true)
#OrderBy("id")
private Set<WizardAnswer> answers = new TreeSet<WizardAnswer>();
But, this will bring the error described at the question above. So it seems that hibernate supported SortedSet and if one just change the line above to
#OneToMany(mappedBy = "question", fetch = FetchType.EAGER, cascade = { CascadeType.ALL }, orphanRemoval=true)
#OrderBy("id")
private SortedSet<WizardAnswer> answers;
it works like magic :)
more info on hibernate SortedSet can be here
There is this bug which looks suspiciously similar: https://hibernate.atlassian.net/browse/HHH-9940.
And the code to reproduce it: https://github.com/abenneke/sandbox/tree/master/hibernate-null-collection/src/test
There are 2 possible fixes to this:
the collection is initialized with an empty collection (instead of null)
orphanRemoval is set to false
Example - was:
#OneToMany(cascade = CascadeType.REMOVE,
mappedBy = "jobEntity", orphanRemoval = true)
private List<JobExecutionEntity> jobExecutionEntities;
became:
#OneToMany(cascade = CascadeType.REMOVE,
mappedBy = "jobEntity")
private List<JobExecutionEntity> jobExecutionEntities;
I had the same issue, but it was when the set was null. Only in the Set collection, in List work fine. You can try to the hibernate annotation #LazyCollection(LazyCollectionOption.FALSE) instead of JPA annotation fetch = FetchType.EAGER.
My solution:
This is my configuration and work fine
#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;
One other cause may be using lombok.
#Builder - causes to save Collections.emptyList() even if you say .myCollection(new ArrayList());
#Singular - ignores the class level defaults and leaves field null even if the class field was declared as myCollection = new ArrayList()
My 2 cents, just spent 2 hours with the same :)
I was getting A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance when I was setting parent.setChildren(new ArrayList<>()). When I changed to parent.getChildren().clear(), it solved the problem.
Check for more details: HibernateException - A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance.
I am using Spring Boot and had this issue with a collection, in spite of not directly overwriting it, because I am declaring an extra field for the same collection with a custom serializer and deserializer in order to provide a more frontend-friendly representation of the data:
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;
}
It seems that even though I am not overwriting the collection myself, the deserialization does it under the hood, triggering this issue all the same. The solution was to change the setter associated with the deserializer so that it would clear the list and add everything, rather than overwrite it:
#JsonDeserialize(using = AttributeDeserializer.class)
public void setAttributesList(List<Attribute> attributes) {
this.attributes.clear();
this.attributes.addAll(attributes);
}
Mine was completely different with Spring Boot!
For me it was not due to setting collection property.
In my tests I was trying to create an entity and was getting this error for another collection that was unused!
After so much trying I just added a #Transactional on the test method and it solved it. Don't no the reason though.
#OneToMany(mappedBy = 'parent', cascade= CascadeType.ALL, orphanRemoval = true)
List<Child> children = new ArrayList<>();
I experienced the same error when I was adding child object to the existing list of Child Objects.
childService.saveOrUpdate(child);
parent.addToChildren(child);
parentService.saveOrUpdate(parent);
What resolved my problem is changing to:
child = childService.saveOrUpdate(child);
Now the child is revive with other details as well and it worked fine.
Had this issue with spring-boot 2.4.1 when running the tests in bulk from [Intellij Idea] version 2020.3. The issue doesn't appear when running only one test at a time from IntelliJ or when running the tests from command line.
Maybe Intellij caching problem?
Follow up:
The problem appears when running tests using the maven-surefire-plugin reuseForks true. Using reuseForks false would provide a quick fix, but the tests running time will increase dramatically. Because we are reusing forks, the database context might become dirty due to other tests that are run - without cleaning the database context afterwards. The obvious solution would be to clean the database context before running a test, but the best one should be to clean up the database context after each test (solving the root cause of the original problem). Using the #Transactional annotation on your test methods will guarantee that your database changes are rolled back at the end of the test methods. See the Spring documentation on transactions: https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-tx.
I face a similar issue where I was using these annotations in my parent entity :
#Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
Mistakenly, I was trying to save a null parent object in database and properly setting values to my entity object resolved my error. So, do check if you are silly setting wrong values or trying to save a null object in database.
Adding my dumb answer. We're using Spring Data Rest. This was our pretty standard relationship. The pattern was used elsewhere.
//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
With the relationship we created, it was always intended that the children would be added through their own repo. I had not yet added the repo. The integration test we had was going through a complete lifecycle of the entity via REST calls so the transactions would close between requests. No repo for the child meant the json had the children as part of the main structure instead of in _embedded. Updates to the parent would then cause problems.
Following solution worked for me
//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;
Instead of assigning new collection
public void setChildren(Set<ChildEntity> children) {
this.children = children;
}
Replace all elements with
public void setChildren(Set<ChildEntity> children) {
Collections.replaceAll(this.children,children);
}
be careful with
BeanUtils.copyProperties(newInsum, insumOld,"code");
This method too break the hibernate.
This is in contrast to the previous answers, I had exactly the same error: "A collection with cascade=”all-delete-orphan” was no longer referenced...." when my setter function looked like this:
public void setTaxCalculationRules(Set<TaxCalculationRule> taxCalculationRules_) {
if( this.taxCalculationRules == null ) {
this.taxCalculationRules = taxCalculationRules_;
} else {
this.taxCalculationRules.retainAll(taxCalculationRules_);
this.taxCalculationRules.addAll(taxCalculationRules_);
}
}
And then it disappeared when I changed it to the simple version:
public void setTaxCalculationRules(Set<TaxCalculationRule> taxCalculationRules_) {
this.taxCalculationRules = taxCalculationRules_;
}
(hibernate versions - tried both 5.4.10 and 4.3.11. Spent several days trying all sorts of solutions before coming back to the simple assignment in the setter. Confused now as to why this so.)
In my case it was concurrent access to one Hibernate Session from several threads.
I had the Spring Boot Batch and RepositoryItemReader implementation where I fetched entities by page request with size 10.
For example my entities are:
#Entity
class JobEntity {
#ManyToOne(fetch = FetchType.LAZY)
private GroupEntity group;
}
#Entity
class GroupEntity {
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<Config> configs;
}
Batch process: reader -> processor -> writer in one transaction.
In that entities configuration, GroupEntity can escapes to other threads:
First thread that entered to read section fetches the page of JobEntity with size 10 (RepositoryItemReader#doRead), this items contain one shared GroupEntity object (because all of them pointed to the same group id). Then it takes the first entity. Next threads that come to read section take JobEntity from this page one by one, until this page will be exhausted.
So now threads have access to the same GroupEntity instance thought the JobEntity instances, that is unsafe multi thread access to the one Hibernate Session.
As of 2021 and Spring Boot 2.5, it helped me to initialize the field right away when declaring it:
#OneToMany(mappedBy="target",fetch= FetchType.EAGER,cascade = CascadeType.ALL, orphanRemoval = true)
private List<TargetEntity> targets = new ArrayList<>();
Issue is solved when we make the child as final..
we should not change the reference of the child in constructor as well as setter.

Categories