In an application using EclipseLink 2.5 and container-managed transactions that needs to merge detached entities from time to time, each entity contains a field with the @Version
annotation. Some implementation of optimistic locking is necessary, since enitities are mappted to DTOs and sent to the client, which might then request an update on these entities based on the changes they have made to the corresponding DTOs. The problem I am facing is that whenever persist()
or merge()
are called on an entity, the corresponding entity being added to the persistence context in the case of persist()
or the updated entity returned by merge()
do not contain the updated version field. To demonstrate this through an example, suppose we have the following entity:
@Entity
public class FooEntity implements Serializable {
@Id
@GeneratedValue(generator = "MyGenerator")
private Long fooId;
@Version
private Long version;
@Column(nullable = false)
private String description;
// generic setters and getters
}
This entity then gets persisted/merged in an EJB in a fashion similar to the following:
@Stateless
public class FooEjb {
@PersistenceContext(unitName = "FooApp")
private EntityManager entityManager;
public FooEntity create() {
FooEntity entity = new FooEntity();
entity.setDescription("bar");
entityManager.persist(entity);
return entity;
}
public FooEntity update(Long fooId, String description) {
FooEntity entityToUpdate = entityManager.find(FooEntity.class, fooId);
if (entityToUpdate != null) {
entityToUpdate.setDescription(description);
return entityManager.merge(entityToUpdate);
}
return null;
}
}
Calling these EJB methods shows the following behavior:
FooEntity newEntity = fooEjbInstance.create();
newEntity.getFooId(); // returns the generated, non-null value; for the sake of example 43L
newEntity.getVersion(); // returns null
entityManager.find(FooEntity.class, 43L).getVersion(); // returns 1L
// entity with fooId == 42L exists and has a version value of 1L
FooEntity updatedEntity = fooEjbInstance.update(42L, "fhtagn");
updatedEntity.getVersion(); // returns the old value, i.e. 1L
entityManager.find(FooEntity.class, 42L).getVersion(); // returns 2L
This makes the returned entity unsuitable for passing to the client, as any state changes made by the client cannot be persisted due to the merge/persist call rightly causing an OptimisticLockException
.
The EJB methods are not explicitly annotated with @TransactionAttribute
, which per JPA specs should cause the default value of TransactionAttributeType.REQUIRED
to be applied. The current theory is that the phenomenon perceived here has to do with the version field being updated only when the transaction is committed. Since by the time one of the EJB methods above returns, its associated transaction has not yet been committed (and will in fact be committed immediately after the method returns), the version field has not yet been updated. There was a mention of in object vs. in cache storing of the version filed in this question, but I have not been able to find definitive documentation on this. Is this as a whole working as designed, either according to JPA 2.0 or the EclipseLink implementation? If so, how could I best deal with the aforementioned problem?
Merge and persist don't need to immediately go to the database, so operations that depend on, triggers, sequencing and versions might need to call flush or commit to have those values set. Flush forces the context to synchronize with the database, and should set the values appropriately in managed objects. ID generation can be set on persist calls - this can happen when sequences allow for pre-allocation, but not usually when identity objects or triggers are used.
Since an EntityManager context represents a transaction, it is completely isolated from other contexts/transactions. Until the transaction commits, version and other changes cannot be picked up by other processes anyway, so it shouldn't matter when synchronization occurs to other processes. JPA states that most exceptions occur either on the persist/merge call or can be delayed until the context synchronizes with the database (flush/commit) depending on the nature of the operation. Optimistic locking is meant for systems where these collisions are infrequent and retries are less expensive than pessimistic locking every operation