JPA error "Cannot merge an entity that has been removed" trying to delete and reinsert a row with SpringData - jpa

I've an Entity (with a primary key that is not generated by a sequence) like this in a Spring Data JPA/Eclipselink environment :
#Entity
#Table(name="MY_ENTITY")
public class MyEntity implements Serializable {
#Id
#Column(insertable=true, updatable=true, nullable=false)
private String propertyid;
\\other columns
}
and I'm trying to delete a row from the table and reinsert it (with the same primary key).
My approach is to call deleteAll() to clean the table and then save() the new Entity :
#Transactional
public void deleteAndSave(MyEntity entity) {
propertyInfoRepository.deleteAll();
propertyInfoRepository.flush(); // <- having it or not, nothing changes
propertyInfoRepository.save(entity);
}
but this gives me this error :
Caused by: java.lang.IllegalArgumentException: Cannot merge an entity that has been removed: com.xxx.MyEntity#1f28c51
at org.eclipse.persistence.internal.sessions.MergeManager.registerObjectForMergeCloneIntoWorkingCopy(MergeManager.java:912)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChangesOfCloneIntoWorkingCopy(MergeManager.java:494)
at org.eclipse.persistence.internal.sessions.MergeManager.mergeChanges(MergeManager.java:271)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.mergeCloneWithReferences(UnitOfWorkImpl.java:3495)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.mergeCloneWithReferences(RepeatableWriteUnitOfWork.java:378)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.mergeCloneWithReferences(UnitOfWorkImpl.java:3455)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.mergeInternal(EntityManagerImpl.java:486)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.merge(EntityManagerImpl.java:463)
....
What am I doing wrong?
I do not understand why it is trying to merge the entity instead of simply reinsert it after its deletion.
Thanks for your help!

Directly to answer your question:
The problem is that the entity that you try to save has already a persistent identity, i.e an ID, which is why your repository will try to merge, and not to persist the entity.
If you see this question it seems that it is triggered (at least) on the level of the Spring Repository, so you might consider overriding the save method of the repository and test whether the problem is still there.

JPA EntityManager keeps track of the state of each managed entity. In your case, you delete the entity and then try to merge it, which raises the exception. I can't tell if your approach is correct (seems weird to delete and then merge) since you don't provide the whole picture but you can try the following:
Assuming em is your EntityManager and entity your entity:
em.remove(entity); //This will perform the delete
MyEntity detachedEntity = em.detach(entity); //Gets a detached copy of the entity, EM will not operated on this unless told to do so (see below)
detachedEntity.setId(null) // Avoid duplicate key violations; Optional since you are deleting the original entity
em.persist(detachedEntity); // This will perform the required insert

Related

How id can be found in Transaction-Scoped Persistence context if it's not in the database

An example from Pro JPA:
#Stateless
public class AuditServiceBean implements AuditService {
#PersistenceContext(unitName = "EmployeeService")
EntityManager em;
public void logTransaction(int empId, String action) {
// verify employee number is valid
if (em.find(Employee.class, empId) == null) {
throw new IllegalArgumentException("Unknown employee id");
}
LogRecord lr = new LogRecord(empId, action);
em.persist(lr);
}
}
#Stateless
public class EmployeeServiceBean implements EmployeeService {
#PersistenceContext(unitName = "EmployeeService")
EntityManager em;
#EJB
AuditService audit;
public void createEmployee(Employee emp) {
em.persist(emp);
audit.logTransaction(emp.getId(), "created employee");
}
// ...
}
And the text:
Even though the newly created Employee is not yet in the database, the
audit bean can find the entity and verify that it exists. This works
because the two beans are actually sharing the same persistence
context.
As far as I understand Id is generated by the database. So how can emp.getId() be passed into audit.logTransaction() if the transaction has not been committed yet and id has not been not generated yet?
it depends on the strategy of GeneratedValue. if you use something like Sequence or Table strategy. usually, persistence provider assign the id to the entities( it has some reserved id based on allocation size) immediately after calling persist method.
but if you use IDENTITY strategy id different provider may act different. for example in hibernate, if you use Identity strategy, it performs the insert statement immediately and fill the id field of entity.
https://thoughts-on-java.org/jpa-generate-primary-keys/ says:
Hibernate requires a primary key value for each managed entity and
therefore has to perform the insert statement immediately.
but in eclipselink, if you use IDENTITY strategy, id will be assigned after flushing. so if you set flush mode to auto(or call flush method) you will have id after persist.
https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Ids/GeneratedValue says:
There is a difference between using IDENTITY and other id generation
strategies: the identifier will not be accessible until after the
insert has occurred – it is the action of inserting that caused the
identifier generation. Due to the fact that insertion of entities is
most often deferred until the commit time, the identifier would not be
available until after the transaction has been flushed or committed.
in implementation UnitOfWorkChangeSet has a collection for new entities which will have no real identity until inserted.
// This collection holds the new objects which will have no real identity until inserted.
protected Map<Class, Map<ObjectChangeSet, ObjectChangeSet>> newObjectChangeSets;
JPA - Returning an auto generated id after persist() is a question that is related to eclipselink.
there are good points at https://forum.hibernate.org/viewtopic.php?p=2384011#p2384011
I am basically referring to some remarks in Java Persistence with
Hibernate. Hibernate's API guarantees that after a call to save() the
entity has an assigned database identifier. Depending on the id
generator type this means that Hibernate might have to issue an INSERT
statement before flush() or commit() is called. This can cause
problems at rollback time. There is a discussion about this on page
490 of Java Persistence with Hibernate.
In JPA persist() does not return a database identifier. For that
reason one could imagine that an implementation holds back the
generation of the identifier until flush or commit time.
Your approach might work fine for now, but you could run into troubles
when changing the id generator or JPA implementation (switching from
Hibernate to something else).
Maybe this is no issue for you, but I just thought I bring it up.

Why does EntityManager insert instead of updating?

I have a main thread where I get an object from database, then close the EntityManager, then put the object into a queue. A worker thread makes some business with the object, then puts it into a finished queue. Then the main thread gets the object from the finished queue and merges the object:
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
// Print the primary key ID of the object,
// it is NOT empty yes it exists in the db.
em.merge(myObject);
em.getTransaction().commit();
em.close();
I confirm the primary key ID of the object by printing it before merging, and yes it exists in the database. Unfortunately it throws a duplicate key exception from MySQL. How? Shouldn't JPA know that the object has an ID and update it, instead of inserting?
The SQL statement that is mentioned in the exception is an INSERT statement, not UPDATE.
The primary key of the entity is below:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "COMPANYID")
private long companyId;
SOLVED
I was setting a List field of the object to the List field of another newly created object. Then, for some reason that I don't understand, EclipseLink thinks that the object is a new object. God knows why. This JPA stuff is extremely counter-intuitive. You have to be truly an expert to use it. Otherwise it is totally useless because anytime you can mess it up. In practice the total headache and time loss it creates is much more than its advantages!
Anyway, I solved the problem by adding the elements of the other object to the old object as follows:
oldObject.getHistories().clear();
for (History history : newObject.getHistories()) {
history.setCompany(oldObject);
oldObject.getHistories().add(history);
}
Now EclipseLink correctly UPDATES instead of inserting new. I will be happy if someone can explain why this behavior?
This is happening because your EntityManager does not know about the object myObject.
For your code to work you should do something like this:
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
Object myObject = em.findById(someId);
em.getTransaction().commit();
object.setName("yourNewName");
.....................
//And now run
em.getTransaction().begin();
em.merge(myObject);
em.getTransaction().commit();
em.close();
Please note that I close the EntityManager only after I have done all my transactions needed to the database.
And myObject is exactly the one that I recieved from my em.findById, I do not simply create a new object, because that will not work.
This is a tutorial that maybe can help you understand a bit more.(you have in the right a playlist with more videos about JPA ).

Multiple representations of the same entity are being merged, Detached

I have been getting an error while trying to update a list of entities containing persisted entity and detached entity both (newly created entity) into my db using jpa2.0.
My entity contains internal entities which are giving an error (mentioned in the title) when merging the data:
Class superclass{
private A a;
private string name;
//getter setters here...
}
Class A{
private long id;
#onetoone(cascade=CascadeType.All, fetch=FetchType.Eager)
private B b;
#onetoone(cascade=CascadeType.All, fetch=FetchType.Eager)
private C c;
//getter setters here...
}
Class Dao{
daoInsert(superclass x){
em.merge(x);
}
}
I want any entity sent for persisting to be merged into the db.
Hibernate does provide solution for this by adding the following to the persistence.xml
Is there something I can do in jpa same as hibernate.
Please do not suggest to find the entity using em.find() and then update manually because I need both entities the persisted entity and the newly created entity too.
Also I'm using spring form to persist the entire patent entity into db.
I am sorry if I'm not clear enough, this is my first question and I'm really a beginner.
Any help will be most appreciated.
Found an answer to the question myself today.You just need to
remove CascadeType.MERGE from the entity that is not allowing you to persist the detached entity.
if you're using CascadeType.ALL then mention all cascade type other than CascadeType.MERGE.
Now removing CascadeType.MERGE from cascade is one solution but not a best solution because after removing MERGE from Cascade you won't be able to update the mapped object ever.
If you want to merge the Detached entity with Hibernate then clear the entity manager before you merge the entity
entityManager.clear();
//perform modification on object
entityManager.merge(object);
To solve this problem make sure to specify that the identifiers of your objects are automatically generated by adding #GeneratedValue(strategy = GenerationType.IDENTITY) on the identifaint such as id.
In this way when the merge will be carried out, the identifier of the elements to merge will be automatically incremented, compared to the other object already recorded in the database to avoid primary key conflicts

Why does a manually defined Spring Data JPA delete query not trigger cascades?

I have following problem: when I try to delete an entity that has following relation:
#OneToMany(mappedBy="pricingScheme", cascade=CascadeType.ALL, orphanRemoval=true)
private Collection<ChargeableElement> chargeableElements;
with a CrudRepository through a provided delete method it removes the entity along with its all chargeable elements which is fine. The problem appears when I try to use my custom delete:
#Modifying
#Query("DELETE FROM PricingScheme p WHERE p.listkeyId = :listkeyId")
void deleteByListkeyId(#Param("listkeyId") Integer listkeyId);
it says:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot delete or update a parent row: a foreign key constraint fails
(`listkey`.`chargeableelements`, CONSTRAINT `FK_pox231t1sfhadv3vy7ahsc1wt`
FOREIGN KEY (`pricingScheme_id`) REFERENCES `pricingschemes` (`id`))
Why I am not allowed to do this? Does #Query methods do not support cascade property? I know I can findByListkeyId(…) first and then remove persistent entity with standard delete method, but it is inelegant. Is it possible to use a custom #Query method the way I tried to?
This has got nothing to do with Spring Data JPA but is the way JPA specifies this to work (section 4.10 - "Bulk Update and Delete Operations", JPA 2.0 specification):
A delete operation only applies to entities of the specified class and its subclasses. It does not cascade to related entities.
If you think about it, JPA cascades are not database-level cascades but ones maintained by the EntityManager. Hence, the EntityManager needs to know about the entity instance to be deleted and its related instances. If you trigger a query, it effectively can't know about those as the persistence provider translates it into SQL and executes it. So there's no way the EntityManager can analyze the object graph as the execution is completely happening in the database.
A question and answer related to this topic here can be found over here.

How to ignore a JPA ManyToOne property with null value on merge after JAXB deserialization?

Example use case:
class Address {
#XMLTransient
#ManyToOne(cascade={})
private Person person;
}
In my use case Address is serialized to XML via JAXB, modified in another system, deserialized from XML to a detached JPA entity and then merged back to db (em.merge(address)). As the Person property is marked #XMLTransient it is restored from XML with null.
As all Address' have a database entry and could be identified by there Id I'd like merge to ignore the Person property and just keep the database value for the relation (this has nothing to do with cascade).
Is there a way to tell JPA to ignore the Person property on merge or would I have to use an #XMLAdapter to set the property with the corresponding Person object before merge (btw I also use optimistic locking with #Version on all entities).
Any hints?
Miguel
It sounds like you don't want this relation to be updated by JPA at all, upon a merge. Is that correct? If so, have you tried setting updatable=false on the #ManyToOne?