Обработка JPA/EclipseLink для @Version

В приложении, использующем 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? Если да, то как мне лучше всего справиться с вышеупомянутой проблемой?


person Lord Zuthulu    schedule 13.03.2017    source источник
comment
Слияние на самом деле не увеличивает поле версии - это происходит только тогда, когда операторы синхронизируются с базой данных, поэтому в поле версии еще нет изменений, когда ваш объект сериализуется обратно вызывающей стороне. Я бы попробовал вызвать em.flush() в методе - это синхронизирует контекст и увеличивает поле до того, как ваш объект будет возвращен.   -  person Chris    schedule 13.03.2017
comment
@ Крис Это имеет смысл. У меня сложилось ошибочное впечатление, что поле версии увеличивается во время слияния, поскольку там выполняются проверки на старое состояние. Однако фактическое поведение кажется мне странным, поскольку, если другая транзакция попытается прочитать и обновить тот же объект после вышеупомянутого слияния, но до сброса, она получит значение старой версии и вызовет OLE при слиянии, если диспетчер сущностей сбросит в это время. Верно ли мое предположение? Если да, то есть ли обоснование такого дизайнерского решения?   -  person Lord Zuthulu    schedule 16.03.2017
comment
Ваш контекст EntityManager представляет собой транзакцию, поэтому он полностью изолирован от других контекстов/транзакций. Пока транзакция не будет зафиксирована, версия и другие изменения не могут быть получены другими процессами. JPA утверждает, что большинство исключений возникают либо при вызове persist/merge, либо могут быть отложены до тех пор, пока контекст не синхронизируется с базой данных (flush/commit) в зависимости от характера операции. Оптимистическая блокировка предназначена для систем, в которых эти коллизии происходят нечасто, а повторные попытки обходятся дешевле, чем пессимистическая блокировка каждой операции.   -  person Chris    schedule 16.03.2017
comment
@Chris Не могли бы вы опубликовать свой комментарий в качестве ответа, чтобы я мог его принять?   -  person Lord Zuthulu    schedule 17.03.2017


Ответы (1)


Слияние и сохранение не требуют немедленного обращения к базе данных, поэтому для операций, которые зависят от триггеров, последовательности и версий, может потребоваться вызов сброса или фиксации, чтобы установить эти значения. Flush заставляет контекст синхронизироваться с базой данных и должен соответствующим образом устанавливать значения в управляемых объектах. Генерация идентификатора может быть установлена ​​для постоянных вызовов — это может произойти, когда последовательности допускают предварительное выделение, но обычно не при использовании объектов идентификации или триггеров.

Поскольку контекст EntityManager представляет собой транзакцию, он полностью изолирован от других контекстов/транзакций. Пока транзакция не будет зафиксирована, версия и другие изменения не могут быть получены другими процессами, поэтому не должно иметь значения, когда происходит синхронизация с другими процессами. JPA утверждает, что большинство исключений возникают либо при вызове persist/merge, либо могут быть отложены до тех пор, пока контекст не синхронизируется с базой данных (flush/commit) в зависимости от характера операции. Оптимистическая блокировка предназначена для систем, в которых эти коллизии происходят нечасто, а повторные попытки обходятся дешевле, чем пессимистическая блокировка каждой операции.

person Chris    schedule 17.03.2017