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);
}
Related
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
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.
I am a bit confused about managing relationship in JPA.
basically I have two entities with a One to Many relationship
A configuration can have have a one or many email list associated with it.
#Entity
public class Config {
#OneToMany(mappedBy="owner",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<Email> emailReceivers;
}
#Entity
public class Email {
#ManyToOne
private Config owner;
}
In an EJB and during update/merge operation wherein I would edit the list of emails associated with a configuration,
I thought that I dont need to explicitly call the delete operation on my email Entity and I would just manage the relationship by deleting the email in my configuration email list.
#Stateless
public class ConfigFacadeImpl implements ConfigFacade{
#EJB
private ConfigDao configDao;
#EJB
private EmailDao emailDao;
#Override
public void update(Config Config, List<Email> emailsForDelete) {
if(emailsForDelete!=null && emailsForDelete.size() > 0){
for(Email emailTemp: emailsForDelete){
Email email = emailDao.find(emailTemp.getId());
emailDao.delete(email); // Do I need to explicitly call the remove??
config.getEmailReceivers().remove(email);
}
}
configDao.update(config);
}
}
If I don't execute the delete and only remove it from the list, it wont erase my table row.
The UI and the database is now not in sync as the UI would not show the email(s) that I have deleted but when you check the database, the row(s) are still there.
Is it required? I thought JPA would handle this for me if I would just remove it in my entities.
UPDATE
I have tweaked my code to get the entity from the database first before making any changes but still it is not deleting my child email entities. I wonder if this is an apache derby issues. (This is the correct way right as I am passing my entities from my JSF managed bean into my EJB so I need to get the sync from the DB first.)
#Override
public void update(Config config, List<Email> emailsForDelete) {
Config configTemp = configDao.find(config.getId());
if(emailsForDelete!=null && emailsForDelete.size() > 0){
for(Email emailTemp: emailsForDelete){
configTemp.getEmailReceivers().remove(emailTemp);
}
}
configDao.update(config);
}
Since you have already defined cascade type = CascadeType.ALL, JPA should take care of the deletion. Explicit Delete statement is not required.
These two statements are not required:
Email email = emailDao.find(emailTemp.getId());
emailDao.delete(email); // Do I need to explicitly call the remove??
Instead, you may want to just find the matching emailReceiver in config.getEmailReceivers() and remove the matching EmailReceivers as you are doing. There is no need to load the Email entity from the database.
EDIT: To delete orphan objects, you may want to include CascadeType.DELETE_ORPHAN cascade attribute along with CascadeType.ALL.
This is the same issue as in Why merging is not cascaded on a one to many relationship
Basically, JPA can only cascade over entities in your collection. So changes to child objects removed from the collection are never putinto the context, and so can't be pushed to the database. In this case, the oneToMany is controlled by the manytones back pointer, so even collection changes won't show up unless the child is also merged. Once a child is pruned from the tree, it needs to be managed and merged individually for changes to it to be picked up.
With JPA 2.0, you can use the option orphanRemoval=true in parent entity
Example:
#Entity
public class Parent {
...
#OneToMany(mappedBy="parentId",cascade=CascadeType.ALL, orphanRemoval=true)
private List<Child> childList;
...
}
Is there any way to force the persistence order of objects in JPA 2 w/Hibernate?
Say I have three classes: Parent, Child, and Desk. Parent owns collections of Child and Desk via #OneToMany; a Child can have one Desk. Furthermore, Parent defines transitive persistence on both collections, but Child does not define transitive persistence on its Desk:
class Parent {
#OneToMany(cascade=CascadeType.ALL) Collection<Child> children;
#OneToMany(cascade=CascadeType.ALL) Collection<Desk> desks;
...
}
class Child {
#OneToOne(cascade={}) Desk desk;
#ManyToOne Parent parent;
}
class Desk {
#ManyToOne Parent parent;
}
Ideally, I'd like to create a Desk and a Child at the same time and persist the relationships:
Parent parent = em.find(...);
Child child = new Child();
Desk desk = new Desk();
// add both desk and child to parent collections here
// set Parent attribute on both desk and child
If I execute the above code in a transaction, Hibernate cascades from Parent to its new Child and attempts to persist the new Child object. Unfortunately, this results in an "object references an unsaved transient instance" error, because the cascade from Parent to Desk hasn't resulted in the new Desk object being persisted yet.
I know I can fix the problem with an EntityManager flush() operation (em.flush()) - create the Child, create the Desk, attach both to Parent, em.flush(), then attach the Desk to Child, but I'm not super-keen on littering my code with em.flush() to save complex graphs of new persistent objects. Is there a different way to signal to JPA 2 or Hibernate that it should always persist the new Desk first instead of the new Child?
Looking at your description, I think that the Persistence system tries to persist first in this order:
First the Parent.children[i]
Each Children[i] has a transient pointer to Desk. The system fails to persist it because you have not configured it as Cascade.Persist.
Then it fails when persisting Desk, and you think that it fails in the path Parent.desks[i] (which is configured as Cascade) but maybe the fail doesn't come from this path.
I have a parent object with a version locking policy defined as follows:
VersionLockingPolicy lockingPolicy = new VersionLockingPolicy();
lockingPolicy.setIsCascaded(true);
lockingPolicy.setWriteLockFieldName("CacheId");
descriptor.setOptimisticLockingPolicy(lockingPolicy);
and with a child mapped as follows:
OneToManyMapping childMapping = new OneToManyMapping();
childMapping.setAttributeName("children");
childMapping.setReferenceClass(Child.class);
childMapping.dontUseIndirection();
childMapping.privateOwnedRelationship();
childMapping.useBatchReading();
childMapping.useCollectionClass(ArrayList.class);
childMapping.addTargetForeignKeyFieldName("Child.ParentId", "Parent.Id");
descriptor.addMapping(childMapping);
When I change a field on the child and update the child cacheId directly on the database, eclipselink queries do not pick up the change. When I then update the cacheId of the parent object, eclipselink queries do return the change to the child field.
I thought the cascaded version locking policy was supposed to cause the parent to update when any of its private owned child objects were updated (as defined by their version fields). Was I wrong about that, or is there likely something wrong somewhere else in my code?
Just use the following on the parent entity class:
#OptimisticLocking(cascade = true)
and mark #OneToMany with #PrivateOwned
This works only if you use version column. Please check:
http://wiki.eclipse.org/Using_EclipseLink_JPA_Extensions_(ELUG)#Using_EclipseLink_JPA_Extensions_for_Optimistic_Locking
I was wrong. There is nothing in the eclipselink code that will do what I wanted.
I think I will simply add a trigger to the child objects to update the parent cacheId.