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:
- 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());
}
- 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 calledMyService
, 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);
}
- Use additional abstraction layers, e.g:
┌────────────┐
│ Controller │
└──────┬─────┘
│
│
┌─────▼─────┐
│ MyService │
└─────┬─────┘
│
│ ┌────────────────────────────────────────────────────┐
│ one rror │@Transactional(propagation=Propagation.REQUIRES_NEW)│
└──────────►│ErrorLogging.saveSomethingOnDBWhenAnErrorOccurs │
└────────────────────────────────────────────────────┘