Announcement Announcement Module
Collapse
No announcement yet.
UnexpectedRollbackException in a rollback JUnit test Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • UnexpectedRollbackException in a rollback JUnit test

    I have a JUnit test that uses AbstractTransactionalDataSourceSpringContextTests. I recently upgraded from Spring 1.1.5 to 1.2.2 and the test fails (succeeded in 1.1.5) with the following exception:

    Code:
    org.springframework.transaction.UnexpectedRollbackException: Transaction has been rolled back because it has been marked as rollback-only
    	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:417)
    	at org.springframework.transaction.interceptor.TransactionAspectSupport.doCommitTransactionAfterReturning(TransactionAspectSupport.java:258)
    	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:67)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:144)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:174)
    	at $Proxy0.loginCheck(Unknown Source)
    	at com.mycompany.ws.common.LoginCheckInterceptor.before(LoginCheckInterceptor.java:25)
    	at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:144)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:174)
    	at $Proxy44.getShelfInfoList(Unknown Source)
    	at component.service.EquippingServiceImplTests.testMissingInput(EquippingServiceImplTests.java:91)
    It appears to be failing when a MethodBeforeAdvice is executed (LoginCheckInterceptor above which calls a method loginCheck in a class called LoginServiceImpl). loginCheck executes in a transaction, as does the class that is being advised (both using TransactionProxyFactoryBean). In both cases the transaction attributes are PROPAGATION_REQUIRED for all methods.

    Strangely, I haven't seen this occur with other tests that do similar things. The only thing unusual about this test is that it has multiple calls to the same advised method and in each case an exception is thrown and caught (it expects an exception).

    I just tried putting one of these method calls in its own unit test, and it does not exhibit this problem. It only appears when I have more than one calls.

  • #2
    Outside of JTA, that can only happen if an inner transaction has marked the global resource holder as rollback-only - probably as result of its rollback rules -, with the outer transaction not noticing this and still calling commit. To make the caller of the outer transaction aware that what actually happened wasn't a commit, the commit call fails with an UnexpectedRollbackException.

    So I would recommend to check out what's going on in detail. Within the scope of the transaction that fails here, is there an inner transaction (probably PROPAGATION_REQUIRED) that might fail and mark the entire transaction as rollback-only? Why doesn't the exception get propagated from there and cause the outer transaction to cause a rollback as well?

    Maybe the answer is in the aspects that you're weaving in, which don't necessarily see an exception that's passed up the call stack. Anyway, you need to figure out where the transaction gets marked as rollback-only, and why the outer transaction doesn't notice this and still calls commit - with the result being the UnexpectedRollbackException.

    Juergen

    Comment


    • #3
      BTW, this strict UnexpectedRollbackException behavior has been introduced in Spring 1.2.1, to avoid the situation where a commit call results in a rollback without the caller noticing it. This change was made in response to a request from a user who suffered from such unnoticed rollbacks. An example for a bug fix that introduced stronger enforcement of a rule, I guess.

      Juergen

      Comment


      • #4
        I would think the newer behavior better as it could help uncover potential bugs, no?

        - Andy

        Comment


        • #5
          I agree - that's why the new behavior has been introduced in the first place :-) A commit call should not execute without notice that the actual thing that happened was a rollback.

          Juergen

          Comment


          • #6
            Thanks for the response. I think I understand what is going on now, and my unit test was actually performing behavior that should result in the error.

            In case anyone is interested in the details:

            My understanding of AbstractTransactionalDataSourceSpringContextTests and its superclass AbstractTransactionalSpringContextTests (which roll back the transaction at the end of each test) is that it starts a transaction and rolls back the transaction programmatically in the onTearDown() method:

            (line 165 of AbstractTransactionalSpringContextTests)
            Code:
            this.transactionManager.rollback(this.transactionStatus);
            Since my test is calling methods that throw an exception (and are wrapped in a PROPAGATION_REQUIRED transaction) and this triggers a rollback, the transaction (which includes the outer transaction) gets marked as rollback-only, and even though I catch the exception, when I call another method that is wrapped in a transaction (also PROPAGATION_REQUIRED), it fails on commit with this error since the transaction has been set to rollback.

            Comment


            • #7
              Originally posted by nilesh
              The only thing unusual about this test is that it has multiple calls to the same advised method and in each case an exception is thrown and caught (it expects an exception).

              I just tried putting one of these method calls in its own unit test, and it does not exhibit this problem. It only appears when I have more than one calls.
              Hi, I have the exact same problem as nilesh when upgrading from Spring 1.1.5 to 1.2.3. I have a Transactional JUnit test that calls a transactionally advised method that returns an expected exception multiple times. For example:

              Code:
              public class MyTest extends AbstractTransactionalDataSourceSpringContextTests
              {
                ...
                public void testSaveClientForVariousExceptions()
                {
                    ...
                    try
                    {
                      clientService.saveClient(client); // transactionally advised
                      fail("should have thrown SomeException");
                    }
                    catch (SomeException e)
                    {      
                    }
                    ...
                    try
                    {
                      clientService.saveClient(client); // second call causes UnexpectedRollbackException
                      fail("should have thrown SomeOtherException"); 
                    }
                    catch (SomeOtherException e)
                    {      
                    }
                }
                ...
              }
              On the second call to the transactionally advised saveClient() method, I get the UnexpectedRollbackException. Like nilesh, my test worked fine with the older version of Spring.

              I think I understand Juergen's explanation but I'm wondering how to fix my test? I know one way is to split the test up so that each expected exception is tested in its own test method like so:

              Code:
              public class MyTest extends AbstractTransactionalDataSourceSpringContextTests
              {
                ...
                public void testSaveClientForSomeException()
                {
                    ...
                    try
                    {
                      clientService.saveClient(client); // transactionally advised
                      fail("should have thrown SomeException");
                    }
                    catch (SomeException e)
                    {      
                    }
                }
              
                public void testSaveClientForSomeOtherException ()
                {
                    ...
                    try
                    {
                      clientService.saveClient(client);
                      fail("should have thrown SomeOtherException");
                    }
                    catch (SomeOtherException e)
                    {      
                    }
                }
                ...
              }
              However, I'd rather not do that right now because some of my test methods test many for many exceptions. It would be a lot of work to refactor that right now.

              So, I wondering is there another way to fix this? For example, could I override something in AbstractTransactionalDataSourceSpringContextTests? Alternatively, is there something I can call in the catch{} to reset the transaction?

              Comment


              • #8
                First of all, I want to make the point that in the case of transactional methods wrapping other methods which are transactional with a PROPAGATION_REQUIRED propagation setting, which seems to be the case here (the base test class creates a transaction around the test method, but the service method invocations in the test just join in the transaction), it's a bit of a misnomer to talk about an outer and an inner transaction. There is only one transaction, which is being joined by the inner method call.

                Anyway, something weird is going on here if the calls from the inner saveClient() method calls are throwing the unexpected rollback exception. I can not think of any circumstance in this case under which this should ever be considered a real point to commit (and therefore cause the error on that commit), as the method invocation has not actually caused any transaction to be created, and the commit should only happen when the outer (test) method itself returns. So the state of the rollback flag in the transaction should be irrelevant. And if Spring behaves like EJB 2.1 (see section 17.6.2.2) that should certainly be the case.

                Joe, this is all with the same transaction manager, and with transactions set to PROPAGATION_REQUIRED? What is the transaction manager?

                I will ping Juergen here on this, as again, I can't think of a good reason why this should ever happen.

                Colin

                Comment


                • #9
                  Colin, thanks for your reply.

                  Originally posted by Colin Sampaleanu
                  There is only one transaction, which is being joined by the inner method call.
                  Correct. There is only one transaction here, which begins when the test method begins.

                  Originally posted by Colin Sampaleanu
                  Anyway, something weird is going on here if the calls from the inner saveClient() method calls are throwing the unexpected rollback exception. I can not think of any circumstance in this case under which this should ever be considered a real point to commit (and therefore cause the error on that commit), as the method invocation has not actually caused any transaction to be created, and the commit should only happen when the outer (test) method itself returns. So the state of the rollback flag in the transaction should be irrelevant.
                  I agree. I was confused by this behaviour too.

                  Originally posted by Colin Sampaleanu
                  Joe, this is all with the same transaction manager, and with transactions set to PROPAGATION_REQUIRED? What is the transaction manager?
                  Yes, all with the same transaction manager. Yes, transactions were set to PROPAGATION_REQUIRED. The transaction manager was org.springframework.orm.hibernate3.HibernateTransa ctionManager. And in case it wasn't clear, saveClient() is set to PROPAGATION_REQUIRED.

                  I have a similar situation with my tests for deleteClient(). The only difference is that the second call to deleteClient() is NOT expected to throw an exception, whereas the second call to saveClient() is expected to throw an exception. However, the result is the same in both test methods -- the second call to the transactionally advised service method results in a UnexpectedRollbackException, after the first call threw an application exception. Here is the code for testDeleteClient():

                  Code:
                  public class MyTest extends AbstractTransactionalDataSourceSpringContextTests
                  {
                    ...
                    public void testDeleteClient()
                    {
                      final long someClientId = 1; // NOT allowed to delete this client
                      Client client = (Client) clientService.loadClient(someClientId);
                      try
                      {
                        clientService.deleteClient(client); // transactionally advised
                        fail("should have thrown DeleteNotAllowedException"); 
                      }
                      catch (DeleteNotAllowedException e)
                      {      
                      }
                      ...
                      final long someOtherClientId = 2; // this client can be deleted
                      client = (Client) clientService.loadClient(someOtherClientId);
                      clientService.deleteClient(client); // second call causes UnexpectedRollbackException
                      ...
                    }
                    ...
                  }
                  I'm not sure whether this is considered to be a bug in the framework or not. I leave that up to the Spring team to decide. If you do consider it a bug, please let me know and I'll happily post a bug in JIRA.

                  Anyway, I'm still left with some tests that don't pass. Any advice as to how I should proceed? Should I restructure my tests so that each expected exception is tested in a separate test method? Or is there some way I can workaround this by subclassing AbstractTransactionalDataSourceSpringContextTests and overriding some behaviour?

                  Cheers

                  Comment


                  • #10
                    Sorry for 'piggy-backing' on this post - I had a similar problem with the same exception when using readOnly transactions: http://forum.springframework.org/showthread.php?t=17730

                    Comment


                    • #11
                      cyboc,

                      Have you try split up the test method into separate test methods, one for each expected exception?

                      Comment


                      • #12
                        Originally posted by steve_smith
                        Have you try split up the test method into separate test methods, one for each expected exception?
                        Not yet because that would be a lot of work at this point. I am waiting for someone to (hopefully) suggest an easier solution to this problem.

                        Also, can anyone confirm whether this should be considered a bug? Colin Sampaleanu seemed to suggest that it may be a bug.

                        Comment


                        • #13
                          FYI, I gave up waiting for suggestions for a semi-quick fix. So, I bit the bullet and rejigged my TestCase to use a separate test method for each expected exception so as to avoid the UnexpectedRollbackException. But I would still like to know whether this is a bug (as Colin suggested it might be) and whether I should report it to JIRA.

                          Comment


                          • #14
                            The best thing to do is to add it as a bug in Jira (also pointing to this thread from there).

                            I can not think how the behaviour could be considered correct. Even if Juergen ultimately disagrees, somebody else searching for the issue in Jira will have an idea as to the rationale for it working the way it does.

                            Regards,

                            Comment


                            • #15
                              Hi,

                              we also have the same problem. Now I read all of this, it reminds me of another situation :

                              I set transaction on every *Service bean, get* gets read-only, others get propagation required. If a first Service bean calls a second service bean, and the second fails with an exception, that the first call catch and handle, then the transaction is still a rollback, because the exception went through the transaction AOP around the second service. The point is : why the transaction is marked as rollback ? I would expect the transaction de rollback is an exception tries to get out of the transaction. In our case, the second call is inside the transaction, and the exception doesn't get out at all.

                              Would be interesting to see whether this behaviour still happen with other transaction managers (jta for example).

                              Until this "buggy" behaviour is fixed (or declared as "feature/intended") I'll cut my tests and continue to warn my developers against throwing exceptions out of their beans.

                              Comment

                              Working...
                              X