Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call a custom rollback method in Spring Transaction Management?

Environment: Spring 3, Custom Transaction Management, JDBC Transactions

I just read the Spring docs on using the transaction template to handle transaction management. It seemed overly complex so I want to ask:

Most of my transactions are JDBC related, meaning I just declare an @Transactional on my service. But now I am making a REST service call to another site which needs to rollback if any of the following JDBC operations fail, I'll provide the rollback code in this case.

As I progress in my method, in my transaction - I want to save a reference to the REST service call (needed to roll back that action), and upon exception I just want a method myCustomRollback() called which can access the previously stored object.

Why not just provide a map in the transactionTemplate for storing stuff and define a custom rollback method on the @Transactional annotation?

This is the way I think about it, I'm not following the way Spring thinks about this. Can someone help me bridge the gap between what I want and how I accomplish it most efficiently in Spring? I only need to do this for a few special case operations.

like image 278
David Parks Avatar asked Apr 16 '11 04:04

David Parks


4 Answers

To anyone still reading this:

I solved a similar problem with spring events - as suggested by Den Roman in option 3. Here's the basic idea (scenario is fictional):

Whenever I perform external operations that need to be rolled back together with the transaction, I publish an event inside my @Transactional method using support from spring (org.springframework.context.ApplicationEventPublisher):

@Transactional
public String placeOrder(Order order) {
    String orderId = orderServiceGateway.createOrder(order);
    applicationEventPublisher.publishEvent(new OrderCreatedEvent(orderId));
    workflowService.startWorkflow(orderId);
    return orderId;
}

The event itself can be any object - I created a POJO with details about the remote entity to be deleted.

Then I registered a special event listener that is bound to a transaction phase - in my case to the rollback:

@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void rollBackOrder(OrderCreatedEvent orderCreatedEvent) {
    String orderId = orderCreatedEvent.getOrderId();
    orderServiceGateway.deleteOrder(orderId);
}

Of course, it's recommended to catch & log the exception from rollback operation, not to lose the original exception from the placeOrder() method.

By default these events are synchronous, but they can be made async by additional configuration.

Here's a very good article on this mechanism, including detailed configuration and pitfalls: Transaction Synchronization and Spring Application Events (DZone)

While I don't like the solution 100% because it clutters the business logic with event publishing stuff and binds to spring, it definitely does what I expect it to do and makes it possible to pass context from the transactional method to the rollback method - which is not available through a traditional try/catch block outside of the transactional method (unless you put your context in the exception itself, which is not very nice).

like image 116
Timi Avatar answered Oct 30 '22 09:10

Timi


1 solution is to implement your own transactional manager by extending a one

2 solution is to use TransactionSynchronizationManager class

3 solution is to use @TransactionalEventListener in case you have Spring 4

like image 37
Den Roman Avatar answered Oct 30 '22 08:10

Den Roman


I've re-read your question a few times and am not sure I understand your question completely. I assume your executing someCode and if that fails you would like to execute myCustomRollback which has some information about someCode. So I'll try to provide a Generic answer.

If you want spring to rollback some code. It will only rollback that which is rollBackAble, like jdbc transactions. Assume you have a method which performs 2 calls.

@Transactional
public void doStuff(SomeEntity entity, File file) {
   persist(entity);
   customFileService.createOnFileSystem(file);
   throw new RunTimeException();
}

So the code above will always rollback. It will undo the persisting of your entity, but not the creation of your file, since that is not managed by Spring transactions, unless you provide custom implementation for it to be.

Second, Spring provides 2 ways of working with transactions:

  • Spring AOP: a proxy is created at runtime which will decorate your code with transactional stuff. If your class would be named MyClass, then Spring will create a class names MyClassProxy, which will wrap your code in transactional code.
  • AspectJ: at compile time your .class file will be adjusted and transactional code will be embedded inside your method.

The aspectJ approach seems harder to configure, but isn't so much and is way easier to use. Since anything which is annotated with @Transactional will be embedded (weaved) with code. For Spring AOP this is not the case. Transactional inner method calls for instance in Spring will be ignored! So aspectJ provides a more intuitive approach.

Back to what I think your question is (the code is all in 1 class):

public void doSomeCode() {
    Object restCall = initialize();
    try {
      execute(restCall);
    } catch (CustomException e) {
      myCustomRollback(restCall; e);
    }
}

@Transactional(rollbackFor = CustomException.class)
private void execute(Object restCall) throws CustomException {
    // jdbc calls..
      restCall = callRest(restCall);
      throw new CustomException();
}

void myCustomRollback(Object restCall, CustomException e) {
   ...
}

The code above will only work with AspectJ! Since your making inner method calls which also seems to be private! AOP at runtime cannot handle this.

So what happens is everything (which is rollbackAble) in execute will be rollbacked. And in doStuff you have information about the objects which were used in execute, you now can use in myCustomRollback to rollback your REST stuff manually.

Not sure if I answered this question properly, but I hope it helps someone with a similar problem.

like image 21
Fico Avatar answered Oct 30 '22 08:10

Fico


Spring transaction management the default behavior for automatic rollback is for unchecked exceptions

so for a custom exception,

@Transactional(rollbackFor = CustomException.class, noRollbackFor = RuntimeException.class)
public void doSomething(...
)

the transaction be rolled back if it there is an exception that matches the specified. If an exception not matches, it is propagated to caller of the service or TransactionRolledBackException wrapper

if you use use the org.springframework.transaction.PlatformTransactionManager it is more manageable handling exceptions than template

check the documentation http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html

like image 42
zudokod Avatar answered Oct 30 '22 09:10

zudokod