Spring Transactions - Prevent rollback after unchecked exceptions (RuntimeException)
Asked Answered
D

1

10

I can't manage to prevent a transaction from rolling back after a RuntimeException.

My env is Spring 4.1 + Hibernate 3.6 + JTA (WebSphereUowTransactionManager) running on Websphere 8.0.

"doStuff" case: works

First off, a simple case that behaves as expected. Since I catch the RuntimeException, the transaction commits and the new resource is created successfully.

@Service("fooService")
public class FooServiceImpl implements IFooService {

    @Transactional
    @Override
    public void doStuff(Resource res){
        authService.createResource(res, "ADMIN");
        try {
            throw new RuntimeException("SOMETHING");
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }

"doStuff2" case: works

The next one is OK as well. I declare the noRollbackFor and it let's the transaction commit:

    @Transactional(noRollbackFor=RuntimeException.class)
    @Override
    public void doStuff2(Resource res){
        authService.createResource(res, "ADMIN");
        throw new RuntimeException("SOMETHING");
    }

"doStuff12" case: does NOT work

And finally the problematic one. The difference is that in this case the exception is raised by the second call to authService.createResource. FYI, authService.createResource is only marked as @Transactional, so the default Propagation configuration applies and it should be joining the calling service's transaction.

    @Transactional(noRollbackFor=RuntimeException.class)
    @Override
    public void doStuff12(Resource res){
        
        authService.createResource(res, "ADMIN");
        try{
            res.setName("EXISTING-RESOURCE");
            authService.createResource(res, "ADMIN");
        }catch(RuntimeException e){
            e.printStackTrace();
        }
    }

Despite catching the RuntimeException and declaring the noRollbackFor attribute the transaction always rolls back. Any explanation??

Log trace info:

org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '',+com.myorg.webapps.exception.ElementoYaExistente
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Invoking WebSphere UOW action: type=1, join=false
org.springframework.transaction.support.TransactionSynchronizationManager TRACE - Initializing transaction synchronization
org.springframework.transaction.interceptor.TransactionInterceptor        TRACE - Getting transaction for [com.myorg.test.service.impl.FooServiceImpl.doStuff12]
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Invoking WebSphere UOW action: type=1, join=true
org.springframework.transaction.interceptor.TransactionInterceptor        TRACE - Getting transaction for [com.myorg.authmgr.service.impl.AuthorizationServiceImpl.createResource]
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Returned from WebSphere UOW action: type=1, join=true
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Creating new transaction with name [null]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Invoking WebSphere UOW action: type=1, join=true
org.springframework.transaction.interceptor.TransactionInterceptor        TRACE - Getting transaction for [com.myorg.authmgr.service.impl.AuthorizationServiceImpl.createResource]
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute TRACE - Applying rules to determine whether transaction should rollback on java.lang.Runtime: Couldn't create the resource, it already exists: EXISTING-RESOURCE
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute TRACE - Winning rollback rule is: null
org.springframework.transaction.interceptor.RuleBasedTransactionAttribute TRACE - No relevant rollback rule found: applying default rules
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Returned from WebSphere UOW action: type=1, join=true
org.springframework.transaction.jta.WebSphereUowTransactionManager        TRACE - Triggering beforeCommit synchronization
org.springframework.transaction.jta.WebSphereUowTransactionManager        TRACE - Triggering beforeCompletion synchronization
org.springframework.transaction.support.TransactionSynchronizationManager TRACE - Clearing transaction synchronization
org.springframework.transaction.jta.WebSphereUowTransactionManager        DEBUG - Returned from WebSphere UOW action: type=1, join=false
Desantis answered 22/1, 2015 at 13:51 Comment(0)
P
10

As far as I know, as soon as a runtime exception is thrown from a transactional method and is intercepted by the transaction interceptor, the transaction is marked as rollback only. Even if this transactional method is called from another transactional method.

This makes sense to me: if the inner method can't recover from an exception, it can't recover, and an outer method shouldn't do as if nothing happened.

If you're expecting the transaction not to rollback, you could

  • make the inner method non-transactional
  • configure the inner method not to rollback on this exception
  • have two inner methods:
    • one that is transactional, and is intended to be called when there is no transaction yet, and which simply delegates to the second one
    • one which is not transactional, and is intended to be called as part of an already existing transaction
Pecksniffian answered 22/1, 2015 at 15:5 Comment(1)
What you say kind of makes sense. However, it's weird that the joined transaction doesn't consider the parent's noRollbackFor=RuntimeException.class rule. For me it would be more logical that it adhered to the configuration of the whole transactional scope... In this case, the inner method MUST be transactional and MUST rollback when called on its own so I have to rule out the two first suggestions. About the third one, Spring is sort of forcing us to offer two implementations, one transactional, one not transactional, in our APIs to manage this (not so uncommon) situations, which unelegantDesantis

© 2022 - 2024 — McMap. All rights reserved.