Reputation: 1474
I have a new (i.e. not present on the database yet) JPA entity that is saved to the DB with this bit of code:
public abstract class Dao {
@PersistenceContext(name = "puOpenJPA_Core",type = PersistenceContextType.TRANSACTION)
private EntityManager em;
public void save(Fund entity) {
entity = em.merge(entity);
em.flush();
// do something with entity.fundId
}
}
We need the flush
because we use the id of the object to populate something else and the id is generated on the DB.
The entity looks like this:
@Entity
public class Fund extends AbstractFund implements Serializable
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "fundId")
protected Long id;
// other fields and getters etc.
}
AbstractFund
is an abstract mapped superclass that has more fields.
The table on the database has the fundId
column defined as an identity.
This has all worked fine for a long time. However we're experiencing an intermittent error when the flush
is called. After deployment the code works fine for a while and then suddenly starts throwing this exception:
Caused by: <openjpa-2.3.0-r422266:1540826 fatal general error> org.apache.openjpa.persistence.PersistenceException:
The transaction has been rolled back. See the nested exceptions for details on the errors that occurred.
FailedObject: entities.Fund@1097fef5
at org.apache.openjpa.kernel.BrokerImpl.newFlushException(BrokerImpl.java:2370) [openjpa-all-2.3.0.jar:2.3.0]
at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2207) [openjpa-all-2.3.0.jar:2.3.0]
at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:2105) [openjpa-all-2.3.0.jar:2.3.0]
at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:1876) [openjpa-all-2.3.0.jar:2.3.0]
at org.apache.openjpa.kernel.DelegatingBroker.flush(DelegatingBroker.java:1045) [openjpa-all-2.3.0.jar:2.3.0]
at org.apache.openjpa.persistence.EntityManagerImpl.flush(EntityManagerImpl.java:663) [openjpa-all-2.3.0.jar:2.3.0]
at org.jboss.as.jpa.container.AbstractEntityManager.flush(AbstractEntityManager.java:457) [wildfly-jpa-8.1.0.Final.jar:8.1.0.Final]
......
Caused by: java.lang.Exception: <openjpa-2.3.0-r422266:1540826 fatal store error> org.apache.openjpa.persistence.EntityExistsException:
Cannot insert explicit value for identity column in table 'Fund' when IDENTITY_INSERT is set to OFF. {prepstmnt 2128975916 INSERT INTO Fund
(fundId, ... other columns ...) VALUES (?, ?, ?, )} [code=544, state=23000]
FailedObject: entities.Fund@1e7b1cce
at org.apache.openjpa.util.Exceptions.replaceNestedThrowables(Exceptions.java:255) [openjpa-all-2.3.0.jar:2.3.0]
at org.apache.openjpa.persistence.PersistenceException.writeObject(PersistenceException.java:100) [openjpa-all-2.3.0.jar:2.3.0]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [rt.jar:1.7.0_60]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) [rt.jar:1.7.0_60]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) [rt.jar:1.7.0_60]
at java.lang.reflect.Method.invoke(Method.java:606) [rt.jar:1.7.0_60]
at org.jboss.marshalling.reflect.SerializableClass.callWriteObject(SerializableClass.java:271)
at org.jboss.marshalling.cloner.SerializingCloner.initSerializableClone(SerializingCloner.java:290)
at org.jboss.marshalling.cloner.SerializingCloner.clone(SerializingCloner.java:245)
at org.jboss.marshalling.cloner.SerializingCloner.clone(SerializingCloner.java:125)
at org.jboss.marshalling.cloner.SerializingCloner.cloneFields(SerializingCloner.java:341)
at org.jboss.marshalling.cloner.SerializingCloner.initSerializableClone(SerializingCloner.java:293)
at org.jboss.marshalling.cloner.SerializingCloner.initSerializableClone(SerializingCloner.java:277)
at org.jboss.marshalling.cloner.SerializingCloner.initSerializableClone(SerializingCloner.java:277)
at org.jboss.marshalling.cloner.SerializingCloner.initSerializableClone(SerializingCloner.java:277)
at org.jboss.marshalling.cloner.SerializingCloner.initSerializableClone(SerializingCloner.java:277)
at org.jboss.marshalling.cloner.SerializingCloner.clone(SerializingCloner.java:245)
at org.jboss.marshalling.cloner.SerializingCloner.clone(SerializingCloner.java:125)
at org.jboss.as.ejb3.remote.LocalEjbReceiver.clone(LocalEjbReceiver.java:314) [wildfly-ejb3-8.1.0.Final.jar:8.1.0.Final]
at org.jboss.as.ejb3.remote.LocalEjbReceiver.clone(LocalEjbReceiver.java:297) [wildfly-ejb3-8.1.0.Final.jar:8.1.0.Final]
at org.jboss.as.ejb3.remote.LocalEjbReceiver.processInvocation(LocalEjbReceiver.java:249) [wildfly-ejb3-8.1.0.Final.jar:8.1.0.Final]
... 126 more
I have been unable to recreate this on other environments and am at a loss as to why OpenJPA would suddenly start trying to insert a value for the identity column.
We are using OpenJPA version 2.3.0.
I've tried updating to version 2.4.1 but the problem persists.
Upvotes: 0
Views: 1342
Reputation: 136
First, with all due respect, I'm not sure where Prashant Katara was going with his answer. I'm not aware of a scenario where you need to do the select/copy/persist?? Chris provided some good comments. While I can not say exactly how this issue occurred, let me provide some info on how you could be getting yourself into trouble, possibly sporadic - or environment specific - as you seem to indicate. In your description you posted this 'save' method:
public void save(Fund entity) {
em.merge(entity);
em.flush();
}
You then stated "We need the flush because we use the id.....". There are a few concerning things here. As you can see, you merge 'entity' which is passed into save. However, you never return the object that is returned from 'em.merge'. OpenJPA will likely not populate the id in 'entity', rather it will ONLY populate the id value returned by merge! People always forget that the instance passed to merge is NOT the managed instance! The instance returned from merge is managed! Since you stated that you need the value of the id, I assume you are in fact using 'entity' after the call to save, and as such you should use the managed instance (i.e. the instance returned by 'em.merge'). This is not a safe operation, and I'm surprised you don't get 'null' if you call the getter method for fundId even after the flush. If you NEVER, repeat NEVER, use 'entity' after the call to save then I guess your save method is fine. However, if you intend to continue using the entity after save, then your save should look like this:
public Fund save(Fund entity) {
Fund entityPrime = em.merge(entity);
em.flush();
return entityPrime;
}
Finally, OpenJPA doesn't issue an INSERT which contains the field/column for an IDENTIFICATION field. So you should never see an INSERT with fundId in it. It is beyond me how you have gotten into the case where your INSERT contains fundId. It could be that you possibly have an old version of Fund somewhere on your classpath which doesn't contain '@GeneratedValue(strategy = GenerationType.IDENTITY)'. As part of the INSERT operations, the IDENTIFICATION value is returned from the database, and OpenJPA then assigns that returned value to the (MANAGED) IDENTIFICATION field.
Thanks,
Heath
Upvotes: 2
Reputation: 105
You will need to first retrieve the object then create a new object and copy the properties from retrieved object to the new object and after that call the persist method on the new object.
You can use BeanUtils.copyProperties
method to copy the properties from a source object to a target object or you can do it by your own.
Upvotes: -1