I am trying to remove an Entity that has a one-to-many relationship with another entity, with an Application managed EntityManager. The object seems to be removed just fine but then when another transaction is opened and then committed I get the following error.
Exception in thread "AWT-EventQueue-0" javax.persistence.RollbackException: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: RemovedParentObject[id=1].
My Entities have the following relationship annotation.
In the Parent class
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parentId")
private List<Child> childCollection;
In the child class
#JoinColumn(name = "PARENT_ID", referencedColumnName = "ID", nullable = false)
#ManyToOne(optional = false)
private Parents parentId;
My remove Code is,
// start database transaction window
EntityManager.getTransaction().begin();
// remove all children
for (Child child: parent.getChildCollection())
{
EntityManager.remove(child);
}
parent.getChildCollection().clear();
// remove parent
EntityManager.remove(parent);
// commit chages to the database
EntityManager.getTransaction().commit();
I have tried removing only the children in one transaction, committing it, opening another one and removing the parent and then committing. No change, error occurs.
I have also tried not removing the children, only removing the parent since it has a cascade.ALL annotation. No change, error occurs.
I have tried refreshing all parents and children after removal. the refreshed list does not show the removed parent, but when i next call commit the error occurs.
The parent and each child is managed when this code is called. I am assuming that the remove process is incorrect and that is why the removed object is found when the future commit is executed.
What am I doing incorrectly?
Ensure that no other existing objects are referencing the object you removed. Ensure you are not merging an object that references the deleted object. Are you sure the error is complaining about the deleted object?
Do you create a new EntityManager or use the existing one? Does it work if you create a new one? If you just begin and commit the next transaction without doing anything, does it work?
You could try disabling the shared cache, to see if that affects it.
Also check that the commit is successful, and does not throw an error.
Also try using the latest release.
Related
I have 2 classes in java with relation parent-child and I have a problem with the delete child. when I delete a child the function return true but when I check database nothing happens, the child still not deleted.
this is my class parent : Engagement.java
#OneToMany(fetch=FetchType.EAGER, mappedBy="parent", cascade=CascadeType.ALL)
private Collection<Sub_Engagement> subs_engs;
this is my class child: Sub_Engagement.java
#ManyToOne
#JoinColumn(name="parent")
private Engagement parent;
with this code POST/GET/PATCH work fine but DELETE not working.
I tried a solution like that:
#OneToMany(fetch=FetchType.EAGER, mappedBy="parent", orphanRemoval = true, cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH, CascadeType.REMOVE})
private Collection<Sub_Engagement> subs_engs;
and DELETE works but PATCH/PUT not working when I try update a child.
Thanks in advance :)
Andronicus had it right, that really should work. It's weird that you have an either-or situation with you updates and deletes.
Double check your persistence.xml. Is there something that overrides the annotations?
I also recommend to enable tracing and check the sql queries actually executed. Is the delete statement logged? Is your transaction actually committed?
Can we look at the code you are using for removing the entities? Are you using entitymanager's remove() method or executing some custom jpql?
Also, are you using a CMT (for example EJBs) or are you handling the transactions yourself via JTA?
I have a simple OneToMany Relation between a Parent and a Child.
Parent:
#OneToMany(mappedBy = "parent", orphanRemoval = true, cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
Child:
#ManyToOne(optional = false)
#JoinColumn(name = "PARENT_ID", nullable = false)
private Parent parent;
Because a parent can have a big amount of children I wanted to take advantage of Lazy Instantiation of Indirect Collections:
IndirectList and IndirectSet can be configured not to instantiate the list from the database when you add and remove from them. IndirectList defaults to this behavior. When Set to true, the collection associated with this TransparentIndirection will be setup so as not to instantiate for adds and removes. The weakness of this setting for an IndirectSet is that when the set is not instantiated, if a duplicate element is added, it will not be detected until commit time.
As the default FetchType of OneToMany is LAZY and I am using a List for my Collection, loading a parent from the database causes an IndirectList to be used for the relation. As soon as I add another child to that parent I can see that a select query for the children of that parent is executed.
How can I change that?
I am using Eclipselink 2.6.4 (org.eclipse.persistence:eclipselink:2.6.4).
I also tried to use a DescriptorCustomizer to call org.eclipse.persistence.mappings.CollectionMapping.setUseLazyInstantiationForIndirectCollection(Boolean) on my relation, but this seemed to have absolutely no effect.
After debugging into the Method org.eclipse.persistence.indirection.IndirectList.add(E), I was able to see that the Method call to org.eclipse.persistence.indirection.IndirectList.shouldAvoidInstantiation() at line 206 returned false, because org.eclipse.persistence.indirection.IndirectList._persistence_getPropertyChangeListener() at line 1007 returns null and null is not instanceof AttributeChangeListener. Because of this the relation is then instantiated by org.eclipse.persistence.indirection.IndirectList.getDelegate() in line 216.
To me this seems like a bug, but I don't know enough about this implementation to be sure.
Change tracking is required to support not instantiating lazy collections when making modifications. Change tracking is enabled when using weaving as described here: https://www.eclipse.org/eclipselink/documentation/2.5/concepts/app_dev007.htm
I'm facing a problem with EntityManager.merge() where the merge is cascaded to other entities that have already been deleted from the database. Say I have the following entities:
#Entity
public class Parent {
#OneToMany(cascade = CascadeType.ALL, orphanremoval = true, mappedBy = "parent")
private List<Child> children;
public void clearChildren() { children.clear(); }
public void createChildren(Template template) { ... }
}
#Entity
public class Child {
#ManyToOne
#JoinColumn(name = "parentId")
private Parent parent;
}
The situation where the problem occurs is the following:
The user creates a new Parent instance, and creates new Child instances based on a template of their choosing by calling the createChildren() method. The template defines the amount and properties of the created children.
The user saves the parent, which cascades the persist to the children.
The user notices that the used template was wrong. He changes the template and saves, which results in deletion of the old children and the creation of new ones.
Commonly the deletion of the old children would be handled automatically by the orphanRemoval property, but the Child entity has a multi-column unique index, and some of the new children created based on the new template can have identical values in all columns of the index as some of the original children. When the changes are flushed to the database, JPA performs inserts and updates before deletions (or at least Hibernate does), and a constraint violation occurs. Oracle's deferred constraints would solve this, but we also support MS SQL, which AFAIK doesn't support deferred constraints (correct me if I'm wrong).
So in order to solve this, I manually delete the old children, flush the changes, create the new children, and save my changes. The artificial code snippet below shows the essential parts of what's happening now. Due to the way our framework works, the entities passed to this method are always in a detached state (which I'm afraid is a part of the problem).
public void createNewChildren(Parent parent, Template template) {
for (Child child : parent.getChildren()) {
// Have to run a find since the entities are detached
entityManager.remove(entityManager.find(Child.class, child.getId()));
}
entityManager.flush();
parent.clearChildren();
parent.createChildren(template);
entityManager.merge(parent); // EntityNotFoundException is thrown
}
The last line throws an exception as the EntityManager attempts to load the old children and merge them as well, but fails since they're already deleted. The question is, why does it try to load them in the first place? And more importantly, how can I prevent it? The only thing that comes to my mind that could cause this is a stale cache issue. I can't refresh the parent as it can contain other unsaved changes and those would be lost (plus it's detached). I tried setting the parent reference explicitly to null for each child before deleting them, and I tried to evict the old children from the 2nd level cache after deleting them. Neither helped. We haven't modified the JPA cache settings in any way.
We're using Hibernate 4.3.5.
UPDATE:
We are in fact clearing the children from the parent as well, this was maybe a bit ambiguous originally so I updated the code snippets to make it clear.
Try removing the children from parent before deleting them, that way MERGE can't be cascaded to them because they are not in the parent's collection.
for (Child child : parent.getChildren()) {
// Have to run a find since the entities are detached
Child c = entityManager.find(Child.class, child.getId());
parent.getChildren().remove(c); // ensure that the child is actually removed
entityManager.remove(c);
}
UPDATE
I still think the order of operations is the cause of the problems here, try if this works
public void createNewChildren(Parent parent, Template template) {
for (Child child : parent.getChildren()) {
// Have to run a find since the entities are detached
Child c = entityManager.find(Child.class, child.getId());
parent.getChildren().remove(c); // ensure that the child is actually removed
c.setParent(null);
entityManager.remove(c);
}
parent.createChildren(template);
entityManager.merge(parent);
}
I have the following entities with a parent-child relationship:
public class Parent {
#Id #GeneratedValue String id;
#Version Long version;
#OneToMany(mappedBy = "parent", orphanRemoval = true)
#Cascade({CascadeType.ALL})
Set<Child> children;
// getters and setters
}
public class Child {
#Id #GeneratedValue String id;
#ManyToOne
#JoinColumn("parent_id")
Parent parent;
// getters and setters
}
I retrieve a Parent for edit on the web UI by copy properties to a ParentDto, which has a list of ChildDtos.
Once I'm done editing, I send the ParentDto object back and copy all properties into a new Parent object (parent) with a new HashSet to store the Children created from the list of ChildDtos.
Then I call getCurrentSession().update(parent);
The problem
I can add children, update children, but I can't delete children. What is the issue here and how do I resolve it?
Thanks in advance.
You have a bidirectional association, you need to remove from Child class the link to the parent class, try to make Parent reference to null, and also set the Set<Child> to a new HashSet<Child> or whatever your implementation is.
Then save the changes that will remove the children form the table.
This action can only be used in the context of an active transaction.
public void remove(Object entity);
Transitions managed instances to removed. The instances will be deleted from the datastore on the next flush or commit. Accessing a removed entity has undefined results.
For a given entity A, the remove method behaves as follows:
If A is a new entity, it is ignored. However, the remove operation cascades as defined below.
If A is an existing managed entity, it becomes removed.
If A is a removed entity, it is ignored.
If A is a detached entity, an IllegalArgumentException is thrown.
The remove operation recurses on all relation fields of A whose cascades include CascadeType.REMOVE. Read more about entity lifecycle
I'm wondering what to expect when I use cascade = CascadeType.ALL as such,
#OneToMany(
mappedBy = "employeeProfile",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<ProfileEffortAllocation> effortAllocations;
public List<ProfileEffortAllocation> getEffortAllocations() {
if (effortAllocations == null) {
effortAllocations = new ArrayList<>();
}
return effortAllocations;
}
public void setEffortAllocations(List<ProfileEffortAllocation> effortAllocations) {
this.effortAllocations = effortAllocations;
}
I'm finding when I add a new effortAllocation and attempt to save object, but have a validation failure preventing my code from ever reaching session.saveOrUpdate(parentObj), I'm still getting a pk rather than null as if persist is being called on the child OneToMany. Should my parent object call session.saveOrUpdate(parentObj); before I ever see a pk from effortAllocation?
I'd like to point out that the parent object is an existing object and has been loaded from the database with a pk prior to adding a new child record.
When you use CascadeType.ALL, whenever you do any operation on the parent all those operations would also get cascaded to the child.
Yes you should call saveOrUpdate(parent)
In your case as the parent objects are already existing. You could load the existing parent and create a new child and attach the child to parent and when you call saveOrUpdate(parent), it should update the parent and create all those child and relate it to that parent.
Yes it is generating a id for child, because it is trying to create a child due to cascade all and you could have configured it to generate id in #Id.
Enable sql logs using hibernate.show_sql to understand better whats happening.
I assume you would have a #JoinColumn in your child which would map to the parent primary key.
The cause of this issue was do to a lookup query triggering a flush prior to returning it's results. The solution was to set this.session.setFlushMode(FlushMode.COMMIT);
Hibernate tries to ensure that database conents is up-to-date before making any queries.
https://forum.hibernate.org/viewtopic.php?p=2316849