В приложении, использующем EclipseLink 2.5 и управляемые контейнером транзакции, которым необходимо время от времени объединять отсоединенные сущности, каждая сущность содержит поле с аннотацией @Version
. Необходима некоторая реализация оптимистической блокировки, поскольку сущности сопоставляются с DTO и отправляются клиенту, который затем может запросить обновление этих сущностей на основе изменений, которые они внесли в соответствующие DTO. Проблема, с которой я сталкиваюсь, заключается в том, что всякий раз, когда persist()
или merge()
вызываются для объекта, соответствующий объект добавляется в контекст сохраняемости в случае persist()
или обновленный объект, возвращаемый merge()
, не содержит поле обновленной версии. Чтобы продемонстрировать это на примере, предположим, что у нас есть следующая сущность:
@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
}
Затем этот объект сохраняется/объединяется в EJB способом, подобным следующему:
@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;
}
}
Вызов этих методов EJB показывает следующее поведение:
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
Это делает возвращенный объект непригодным для передачи клиенту, так как любые изменения состояния, сделанные клиентом, не могут быть сохранены из-за вызова слияния/сохранения, правильно вызывающего ошибку OptimisticLockException
.
Методы EJB не имеют явной аннотации с помощью @TransactionAttribute
, что в соответствии со спецификациями JPA должно привести к применению значения по умолчанию TransactionAttributeType.REQUIRED
. Текущая теория состоит в том, что наблюдаемое здесь явление связано с обновлением поля версии только при фиксации транзакции. Поскольку к тому времени, когда один из вышеперечисленных методов EJB возвращает значение, связанная с ним транзакция еще не зафиксирована (и фактически будет зафиксирована немедленно после возврата метода), поле версии еще не обновлено. Было упомянуто о хранении в объекте и в кеше версии, указанной в этом вопросе, но я не смог чтобы найти окончательную документацию по этому вопросу. Работает ли это в целом так, как задумано, в соответствии с JPA 2.0 или реализацией EclipseLink? Если да, то как мне лучше всего справиться с вышеупомянутой проблемой?