Spring Аннотации транзакций - выполнение при успешном завершении

Я использую события приложения Spring на моем уровне обслуживания, чтобы уведомлять более широкую систему о возникновении определенных событий. Проблема заключается в том, что события запускаются в транзакционном контексте (я использую аннотацию Spring @Transactional). Опасность состоит в том, что я запускаю событие, а затем транзакция завершается с ошибкой при совершении (может произойти при использовании, например, Hibernate). В этом случае слушатели событий будут принимать какое-то состояние, которое затем будет откатываться после их выполнения. Опасность здесь заключается в том, что я могу, например, отправить электронное письмо, чтобы подтвердить регистрацию пользователя на веб-сайте, когда на самом деле их учетная запись пользователя фактически не была создана.

Есть ли способ, сохраняющий использование аннотаций, где-то флаг события, которое должно быть запущено после совершения транзакции? Кажется, это похоже на использование SwingUtilities.invokeLater(Runnable runnable) при программировании GUI в Java Swing. Я хочу сказать, выполнить этот бит кода позже, как только текущая транзакция завершится успешно.

Любые идеи?

Спасибо,

Эндрю

Ответ 1

Правильный способ решить вашу проблему с помощью писем с подтверждением, вероятно, заключается в использовании распределенных транзакций с участием участвующих в них ресурсов JMS.

Однако, как быстрое и грязное решение, вы можете попытаться создать обертку вокруг PlatformTransactionManager, которая выполнит Runnable зарегистрирован в некотором хранилище ThreadLocal после успешной фиксации (и удалите его из ThreadLocal после отката). На самом деле, AbstractPlatformTransactionManager имеет именно такую ​​логику с TransactionSynchronization s, но она слишком глубоко погружена в детали ее реализации.

Ответ 2

Это работает для меня:

TransactionSynchronizationManager.registerSynchronization(
    new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            // things to do when commited
        }
        // other methods to override are available too
    });

Ответ 3

Вы можете использовать TransactionSynchronizationManager, не требуя взломать PlatformTransactionManager.

Примечание. TransactionAware является интерфейсом маркера, указывающим, что ApplicationListener хочет получить событие после успешного совершения транзакции.

public class TransactionAwareApplicationEventMulticaster extends SimpleApplicationEventMulticaster {

    @Override
    public void multicastEvent(ApplicationEvent event) {
        for (ApplicationListener listener : getApplicationListeners(event)) {
            if ((listener instanceof TransactionAware) && TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(
                    new EventTransactionSynchronization(listener, event));
            }
            else {
                notifyEvent(listener, event);
            }
        }
     }

     void notifyEvent(final ApplicationListener listener, final ApplicationEvent event) {
          Executor executor = getTaskExecutor();
          if (executor != null) {
               executor.execute(new Runnable() {
                    public void run() {
                         listener.onApplicationEvent(event);
                    }
               });
          }
          else {
               listener.onApplicationEvent(event);
          }
     }

    class EventTransactionSynchronization extends TransactionSynchronizationAdapter {
        private final ApplicationListener listener;
        private final ApplicationEvent event;

        EventTransactionSynchronization(ApplicationListener listener, ApplicationEvent event) {
            this.listener = listener;
            this.event = event;
        }

        @Override
        public void afterCompletion(int status) {
            if ((phase == TransactionPhase.AFTER_SUCCESS)
                    && (status == TransactionSynchronization.STATUS_COMMITTED)) {
                notifyEvent(listener, event);
            }
        }
    }
}