Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Transaction Annotations - Execute on Success

I'm using Spring application events within my service layer to notify the wider system when specific events occur. The issue I have is that the events are fired within a transactional context (I'm using Spring's @Transactional annotation). The danger is that I fire an event and then the transaction fails on commit (can happen when using, for example, Hibernate). In this scenario, the event listeners will be assuming some state that will then be rolled back after they've executed. The danger here is that I may, for example, have sent an email to confirm a user's registration on a website when, in fact, their user account was not actually created.

Is there a way, preserving the use of annotations, to somewhere flag an event to be fired after the transaction commits? It kind of seems somewhat analogous to using SwingUtilities.invokeLater(Runnable runnable) when doing GUI programming in Java Swing. I want to say, execute this bit of code later on once the current transaction commits successfully.

Any ideas?

Thanks,

Andrew

like image 783
DrewEaster Avatar asked Aug 16 '10 10:08

DrewEaster


People also ask

What does @transactional annotation do in spring?

The @Transactional annotation makes use of the attributes rollbackFor or rollbackForClassName to rollback the transactions, and the attributes noRollbackFor or noRollbackForClassName to avoid rollback on listed exceptions. The default rollback behavior in the declarative approach will rollback on runtime exceptions.

What is the purpose of @transactional annotation?

So when you annotate a method with @Transactional , Spring dynamically creates a proxy that implements the same interface(s) as the class you're annotating. And when clients make calls into your object, the calls are intercepted and the behaviors injected via the proxy mechanism.

When @transactional is used on top of a class?

@Transactional can be used on top of class or method, in classes or interfaces. If used on top of class, it applies to all public methods in this class.

Does @transactional works on private method?

The answer your question is no - @Transactional will have no effect if used to annotate private methods. The proxy generator will ignore them. When using proxies, you should apply the @Transactional annotation only to methods with public visibility.


2 Answers

This works for me:

TransactionSynchronizationManager.registerSynchronization(
    new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            // things to do when commited
        }
        // other methods to override are available too
    });
like image 73
Oliv Avatar answered Sep 20 '22 05:09

Oliv


You can make use of TransactionSynchronizationManager without needing to hack PlatformTransactionManager.

Note: TransactionAware is a marker interface indicating that a ApplicationListener wants to receive a Event after transaction is committed successfully.

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);
            }
        }
    }
}
like image 24
clotho Avatar answered Sep 19 '22 05:09

clotho