Повторная попытка отката транзакции JPA и восстановление: объединение объекта с автоматически увеличивающимся @Version

Я хочу восстановить после неудачной транзакции.

Теперь, конечно же, после любого отката все сущности отключаются, а диспетчер сущностей закрывается. Однако пользовательский интерфейс по-прежнему содержит отдельные объекты. Очевидно, что мы не можем просто выбросить изменения пользователя, поэтому мы хотели бы позволить им повторить попытку (исправить выделенную ошибку проверки, затем снова нажать кнопку).

Следуя Java Persistence WikiBook,

Один из методов обработки ошибок - вызвать слияние для каждого управляемого объекта после сбоя фиксации в новом EntityManager, а затем попытаться зафиксировать новый EntityManager. Одна из проблем может заключаться в том, что любые назначенные идентификаторы или оптимистичные версии блокировки, которые были назначены или увеличены, могут нуждаться в сбросе. Кроме того, если исходный EntityManager был РАСШИРЕН, любые используемые объекты все равно будут отсоединены, и их необходимо будет сбросить.

Поначалу этот вариант кажется простым, пока мы неизбежно не столкнемся именно с теми ожидаемыми проблемами. Некоторые службы могут запускать сброс по разным причинам, который увеличивает @Version поля как в БД (которая откатывается), так и в объектах Java (чего нет). Следующее "сохранение" вызывает merge, что вызывает неожиданный OptimisticLockException.

Есть ли надежный способ "откатить" поля версии в объектных компонентах Java?

Хорошо, это сложно. У нас есть каскадные сущности со своими собственными @Versions, поэтому делать это вручную кажется хрупким. (В любом случае, как мы можем достоверно узнать исходные (сохраненные) версии? Не можем запросить, потому что какой-то другой пользователь может успешно обновить объект за это время; запрос текущей версии может нарушить автоматическую блокировку!)

Другой более сложный метод обработки ошибок - всегда работать с нетранзакционным EntityManager. Когда пришло время фиксировать, создается новый EntityManager, нетранзакционные объекты объединяются в него, и новый EntityManager фиксируется. Если фиксация завершается неудачно, только состояние нового EntityManager может быть несогласованным, исходный EntityManager не будет затронут. Это может позволить исправить проблему и повторно объединить EntityManager с другим новым EntityManager. Если фиксация успешна, любые изменения фиксации могут быть объединены обратно в исходный EntityManager, который затем можно продолжать использовать в обычном режиме. Это решение требует значительных накладных расходов, поэтому его следует использовать только в том случае, если действительно требуется обработка ошибок, а поставщик JPA не предоставляет альтернатив.

Это кажется логичным. Есть ли у кого-нибудь опыт реализации такого рода восстановления с двумя EntityManager (особенно с Spring)? Есть ли подводные камни, о которых я должен знать, прежде чем пытаться это сделать? Похоже, что теперь каждая служба и DAO должны знать о двух менеджерах сущностей (в Spring сегодня они почти не зависят от уровня персистентности). Операции поиска DAO используют один EM; 'update' использует другое. Или используйте отдельные DAO для чтения и записи. Ой.

Другие варианты, которые я рассмотрел, включают:

  • Используйте DTO в пользовательском интерфейсе, поэтому автоинкремент ни на что не влияет. Уродливый.
  • Переместите вызов merge в конец любой составной операции. Сущность присоединяется только после успешной проверки и обновления состояния. Кажется странным, что служба "сохранения" больше не merge (только проверяет). Фактически, пользовательский интерфейс возьмет на себя ответственность за вызов DAO! Это так необычно, как кажется?

Совет? Спасибо :-)

Обновить Моя архитектура включает в себя:

  • Отдельные сущности обновляются пользовательским интерфейсом (JSF)
  • Идентификаторы объектов НЕ генерируются автоматически (предварительно назначенные UUID и / или бизнес-ключи)
  • Сущности имеют автоматически увеличивающиеся @Version поля для блокировки
  • Служба "Сохранить" проверяет, вызывает em.merge (JPA через Hibernate)
  • Сервисы "Process" проверяют, применяют бизнес-логику, обновляют состояние объекта
  • Services can be composed. One UI button might do
    1. (Spring @Transactional advice around UI controller: begin)
    2. Сохранять
    3. Процесс 1
    4. Процесс 2
    5. (@Transactional: совершить)
  • Любая служба может вызвать исключение проверки (JSR 303). , который откатывается, как ожидалось (сообщения отображаются в пользовательском интерфейсе)

person Peter Davis    schedule 26.06.2011    source источник
comment
Если ваш пользовательский интерфейс содержит отсоединенные объекты, то эти объекты играют ту же роль, что и DTO: слияние копирует только состояние отсоединенных объектов в присоединенные, а отсоединенные объекты остаются как есть (т.е. их поле версии не должно изменяться, независимо от того, выполняется ли транзакция. совершено или нет).   -  person JB Nizet    schedule 26.06.2011
comment
@JB, Марсель украл твой ответ. Прокомментировал там   -  person Peter Davis    schedule 27.06.2011
comment
В этом контексте использование EntityManager.merge() опасно. См. JPA 2.1, раздел 3.3.3 Откат транзакции. В нем говорится: [..] состояние атрибутов версии и сгенерированное состояние (например, сгенерированные первичные ключи) могут быть несовместимыми. Как указывается в этом разделе, передача такой обособленной сущности в операцию слияния может завершиться ошибкой. В разделе 3.2.7.1 «Слияние состояния отдельного объекта» есть объяснение: любые столбцы версии, используемые объектом, должны проверяться реализацией постоянства времени выполнения во время операции слияния [..].   -  person Martin Andersson    schedule 03.11.2014


Ответы (4)


Вы задаете два разных вопроса, а затем обращаетесь за советом. Я иду с советом (я бы написал это в комментарии, но ТАК пока не разрешает мне комментировать ...)

Использование DTO (или любой структуры данных только для пользовательского интерфейса) в пользовательском интерфейсе не уродливо - это разумно с архитектурной точки зрения. Я согласен с Дж. Б. Низетом в том, что нетранзакционный подход EM по сути превращает ваши сущности в DTO. Если вы должны использовать сущности в своем пользовательском интерфейсе (и я понимаю, что Spring поощряет это), это правильный путь.

По моему опыту, основная причина, по которой люди хотят использовать сущности в качестве временного хранилища пользовательского интерфейса, - это получить доступ к сложной логике проверки в нем. Вы также можете подумать о рефакторинге, чтобы сделать эту логику доступной, не вызывая полную модель предметной области.

person jmetcher    schedule 12.04.2014

Может поддерживать только комментарий JB Nizet: слияние копирует только состояние отдельных объектов.

person Marcel Stör    schedule 26.06.2011
comment
Если поле версии пользовательского интерфейса не обновляется, то не нарушена ли автоматическая блокировка? - person Peter Davis; 27.06.2011

Очевидно, мы не можем просто выбросить изменения пользователя

если пользователь пытается обновить, а кто-то уже изменил это, пользователю лучше обновить копию и снова внести изменения нет?

person Kalpesh Soni    schedule 11.04.2014

Поскольку EntityManager.merge() создает копию переданного объекта, решение кажется довольно простым: поддерживать ссылку на переданный объект в пользовательском интерфейсе (bean-объект JSF) до тех пор, пока транзакция не завершится успешно, и только затем обновлять ссылку с помощью скопированная сущность:

@ManagedBean
@SomeScoped
public class EntityBean
{
    @EJB
    private PersistenceService service;

    private Object entity;

    public void update()
    {
        try
        {
            // service.update() is a CMT method
            Object updatedEntity = service.update(entity);

            // if we are here transaction has completed successfully, so update the reference 
            entity = updatedEntity;
        }
        catch(Exception e)
        {
            // if catched, entity has not been modified (just like transaction never happened)
            // now handle exception (log, add a FacesMessage, rethrow, ...)
        }
    }

    ...
}

Вы можете повторно визуализировать пользовательский интерфейс, и UIComponents будут использовать ту же самую сущность, которую пользователь заполнил перед отправкой предыдущего запроса.

Таким образом, вам не нужно беспокоиться ни о сбросе @Id, @Version, ни о каком-либо другом измененном поле или свойстве, включая каскадные ассоциации, ни об использовании нескольких EntityManager, ни об использовании расширенного контекста сохраняемости.

Тем не менее, у этого простого примера есть некоторые ограничения:

  1. никогда не используйте EntityManager.persist для "переданной" сущности, поскольку при сохранении не выполняется копирование. Вы должны всегда использовать EntityManager.merge, но бывают случаи, когда это просто невозможно.

  2. переданные сущности должны быть в состоянии «ПЕРЕХОДНЫЙ» или «ОТКЛЮЧЕННЫЙ», чтобы EntityManager.merge могла создать копию (в противном случае переданная сущность возвращается, а копия не выполняется). Это означает, что использование расширенного контекста постоянства или OpenSessionInViewFilter и производных строго запрещено.

  3. Следует проявлять особую осторожность при реализации композиции службы с суб-транзакциями: вы можете закончить с завинченными сущностями, когда суб-транзакция завершится успешно, но родительский откат.

Обратите внимание, что я не знаю, как работает Spring, поэтому все это может быть просто неприменимо.

person Michele Mariotti    schedule 06.07.2016