user1555190
user1555190

Reputation: 3243

Creating a post commit when using transaction in Spring

Due to certain reasons, I have manually performed transaction commit and roll back using Spring PlatformTransactionManager. I want to set up a hook so that a post commit action takes place after transaction has been committed.

By looking at:

void commit(TransactionStatus status) throws TransactionException;

I can't see how I can determine a transaction was successful other than assuming it so if no exception are thrown.

And I could use AOP as one option, but what about programmatically doing it, maybe using callback method?

Upvotes: 51

Views: 66647

Answers (4)

Frank Wilson
Frank Wilson

Reputation: 3250

Since Spring 4.2, it has been possible to define listeners for post commit events (or more generally transaction synchronization events e.g. rollbacks) using annotation based configuration. This is based off event handling in core spring. It is easier to test code using this approach since you avoid a direct dependency on TransactionSynchronizationManager, which will likely not be active in a unit test. You can easily test that your transactional service publishes an event and also that your listener performs the right action when you receive an event.

So, without further ado, this is how to set it up:

In this example, we'll assume you have a Customer entity and a CustomerRepository (and ORM to go with it).

First, you need a new event type NewCustomerEvent:

// NewCustomerEvent.java
// Just a regular pojo for the event
public class NewCustomerEvent {

    public String email;

    // constructor, setters and getters omitted
}

Then, you define a listener using @TransactionalEventListener. By default, this will execute after a successful commit but this can be changed using the phase parameter:

// NewCustomerEventListener.java
@Component
public class NewCustomerEventListener {

    @TransactionalEventListener
    public void handleNewCustomerEvent(NewCustomerEvent newCustomerEvent) {
        // handle new customer event
    }
}

Finally, you augment your transaction service with an ApplicationEventPublisher on which you call publish once all the transaction statements have been sent.

// CustomerRespositoryService.java
@Service
public class CustomerRepositoryService {

    @Inject
    private ApplicationEventPublisher applicationEventPublisher;

    @Inject
    private CustomerRepository customerRepository;

    @Transactional
    public void createCustomer(String email) {
        Customer customer = new Customer(email);
        customerRespotory.save(customer);
        applicationEventPublisher.publish(new NewCustomerEvent(email));
    }

}

See also:

Upvotes: 23

Michail Michailidis
Michail Michailidis

Reputation: 12181

Credit to Grooveek's answer and Alex's comment under it - I put this here because the combined suggestions provide a solid and cleaner solution that is hard to find around the net.

Using Spring 4+. if you need a callback on a @Transactional method after it successfully commits just add that in the beginning of the method:

@Service
public class OneService {

    @Autowired
    OneDao dao;

    @Transactional
    public void a transactionalMethod() {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
            public void afterCommit(){
                //do stuff right after commit
                System.out.println("commit!!!");
                
            }
        });
        //do db stuff
        dao.save();
    }
}

Upvotes: 35

n1ckolas
n1ckolas

Reputation: 4440

In one of my projects because of certain reasons I also had to use PlatformTransactionManager. So I forced to use org.springframework.transaction.support.TransactionTemplate.

http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/transaction/support/TransactionTemplate.html

The main benefit is that if you have implemented PlatformTransactionManager correctly, you don't need to bother with manual commit/rollback. At least source code of TransactionTemplate may help you if you need more specific thing.

It's pretty simply to use:

config.xml

<bean name="transactionTemplate"
            class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="platformTransactionManager"/>
</bean>

MyServiceImpl.java

@Service
public class MyServiceImpl implements MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public Entity getSomethingWithTx(final long id) {

        return transactionTemplate.execute(new TransactionCallback&lt;Entity>() {
            @Override
            public Entity doInTransaction(TransactionStatus status) {
                //TODO implement
            }
        });
    }
}

Upvotes: 1

Grooveek
Grooveek

Reputation: 10094

You could get exactly what you want by a simpler way, with TransactionSynchronizationManager and TransactionSynchronization

With TransactionSynchronizationManager, you have static methods to get information about current transaction, and you can register a TransactionSynchronization wich allows you to automatically do a post-commit as you call that

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
           void afterCommit(){
                //do what you want to do after commit
           }
})

Be aware that the TransactionSynchronization is on a per-thread basis (which is often not a problem for a basic web request).

Upvotes: 95

Related Questions