Why does EntityManager insert instead of updating? - jpa

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 ).

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.

JPA CriteriaDelete with huge parameters

I am using JPA 2.1, Oracle DB and have a list of ids for entities to be removed (about 430000 ids). At first, it was implemented as splitting that id list into each smaller one with 1000 ids, pass them as parameters for a JPQL and executing.
delete from SOPFilter f where f.id in (?1)
Then, I want to change to use JPA CriteriaDelete.
CriteriaDelete<SOPFilter> criteriaDelete = cb.createCriteriaDelete(SOPFilter.class);
Root<SOPFilter> from = criteriaDelete.from(SOPFilter.class);
criteriaDelete.where(from.get(SOPFilter_.id).in(sopFilterIds));
It runs fine until it reach the 90000th one and there is a runtime exception cause it to stop here
org.hibernate.SessionException: Session is closed
and make entity manager factory to close.
INFO : bernate.impl.StmpContainerEntityManagerFactoryBean: Closing JPA EntityManagerFactory for persistence unit 'IMOFFERINGMANAGEMENT'
For whom was mislead by my first post with this exception
java.lang.IllegalStateException: EntityManagerFactory is closed
There was a catch clause to handle runtime exception by adding a record to database before throwing it. And to add a event record, it attempts to create another entity manger from the factory which is closed now.
public static void logEvent(EntityManager em) {
EntityManager em2 = null;
EntityManagerFactory emFactory = em.getEntityManagerFactory();
em2 = emFactory.createEntityManager();
// ...
}
Could anyone shed some light on it?
Im not clear on your code but you are likely hitting a transaction timeout. You can set a hint -
query.setHint("javax.persistence.query.timeout", 8000);
There may also be timeouts on the database side

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

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

Tracking last change to an object with #Version annotation in EclipseLink

Using JPA with EclipseLink, I would like to track the timestamp of the last update made to an entity instance. Assuming that this would be easy to combine with optimistic locking, I defined the entity as follows:
import javax.persistence.Version;
[...]
#Entity
public class Foo {
#Id int id;
#Version Timestamp lastChange;
[...]
}
Updating a changed object is done with the following code:
EntityManager em = Persistence.createEntityManagerFactory("myConfiguration");
em.getTransaction().begin();
em.merge(foo);
em.getTransaction().commit();
I would expect that foo.lastChange would be set to the new timestamp each time an update to a changed instance is committed. However, while the field LASTCHANGE is updated in the database, it is not updated in the object itself. A second attempt to save the same object again thus fails with an OptimisticLockException. I know that EclipseLink allows to choose between storing the version-field in cache or directly in the object and I made sure that the configuration is set to IN_OBJECT.
The obvious question is: How to get the foo.lastChange field set to the updated timestamp value when saving to the database? Would
foo = em.find(Foo.class, foo.id);
be the only option? I suspect there must be a simpler way to this.
merge does not modify its argument. It copies the state from its argument to the attached version of its argument, and returns the attached version. You should thus use
foo = em.merge(foo);
// ...
return foo;

openjpa throws optimisticklockexception

I am trying openjpa and jpa. All I have is one entity class as corresponding table in the database. one of the attributes of the entity is username and corresponding row in the db table has varchar2(20). and in my main method what i tried to persist and instance of the entity with username longer than 20.
All I am doing is
em.getTransaction().begin();
em.persist(entity); //entity here is the instance with the username longer than 20
em.getTransaction().commit();
I tried this, hoping to get some other kind of exception, but I don't why I am getting optimisticklockexception.
I do not have any locking setting. I mean I am using default values for locking property.
Does anybody know what's happening here?
Not sure why this happens...I have noticed that the OptimisticLockException can be thrown in weird cases...
Adding a version field to your table and entity can often make OpenJPA work better with locking...
In your entity bean add this (also add the column named VERSION to your table):
private Long version;
#Version
#Column(name="VERSION")
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
Hope this helps...