Home > database >  Exclude private method execution from transaction rollback
Exclude private method execution from transaction rollback

Time:10-26

I have a service method in my project that can be simplified like this:

@Transactional
public void myTransactionalMethod(){
    try {
        soSomethingAndSaveOnDB(); // private method
    }catch (Exception exception){
        saveSomethingOnDBWhenAnErrorOccurs(); // private method
        throw exception;
    }
}

the method is transactional and is executing some main logic that can cause an exception to be thrown. If this appens, any data saved in the DB must be restored to previous state.

But I also need to handle the thrown exception in a catch block and write some additional data in the DB to trace the error.

The problem with my implementation is the final throw exception instruction that causes the transaction rollback and prevents the creation of the data related to error tracing.

I'd like to "disable" transaction rollback for this case, in order to correcly log the error in my database.

NOTE I'm re-throwing the exception to propagate it to the controller and convert it in a HTTP error.

Which could be a good solution to this issue, keeping soSomethingAndSaveOnDB() and saveSomethingOnDBWhenAnErrorOccurs() methods private?

CodePudding user response:

This seems more a workaround than a solution, but at the moment is the best approach I found following the suggestions in the comments.

I modified my method this way:

@Transactional(noRollbackFor = { ExceptionWrapper.class })
public void myTransactionalMethod(){
    try {
        soSomethingAndSaveOnDB(); // private method
    }catch (Exception exception){
        saveSomethingOnDBWhenAnErrorOccurs(); // private method
        throw new ExceptionWrpper(exception);
    }
}

then I defined a class like this:

public class ExceptionWrapper extends RuntimeException {
    private final RuntimeException wrappedThrowable;
}

and finally in my controller I'm catching the new exception type, this way:

try {
    myTransactionalMethod();
}catch(ExceptionWrapper ew){
    throw ew.getWrappedThrowable();
}

CodePudding user response:

There are couple of ways to solve this problem that came to my mind:

  1. Use TransactionTemplate with PROPAGATION_REQUIRES_NEW scope to handle transaction programmatically in saveSomethingOnDBWhenAnErrorOccurs method. This way saving error logs will be done in separate transaction. For example:
    private final TransactionTemplate transactionTemplate;

    private void saveSomethingOnDBWhenAnErrorOccurs(){
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        transactionTemplate.execute(status-> repository.saveErrorLog());
    }
  1. Use of self-injection or ApplicationContext to call logging method. However this way will require to make saveSomethingOnDBWhenAnErrorOccurs method public. For example, suppose that your class called MyService, then:
    @Autowired
    private SomeRepository someRepository;
    @Autowired
    private ApplicationContext applicationContext;

    @Transactional
    public void myTransactionalMethod(){
        try {
            soSomethingAndSaveOnDB(); // private method
        }catch (Exception exception){
            LoggingData loggingData = ...
            applicationContext.getBean(MyService.class).saveSomethingOnDBWhenAnErrorOccurs(someRepository, loggingData); // private method
            throw new ExceptionWrpper(exception);
        }
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Document saveSomethingOnDBWhenAnErrorOccurs(SomeRepository someRepository, LoggingData loggingData) {
        return someRepository.save(document);
    }
  1. Use additional abstraction layers, e.g:
┌────────────┐
│ Controller │
└──────┬─────┘
       │
       │
 ┌─────▼─────┐
 │ MyService │
 └─────┬─────┘
       │
       │           ┌────────────────────────────────────────────────────┐
       │ one rror   │@Transactional(propagation=Propagation.REQUIRES_NEW)│
       └──────────►│ErrorLogging.saveSomethingOnDBWhenAnErrorOccurs     │
                   └────────────────────────────────────────────────────┘
  • Related