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

  • UnexpectedRollbackException when running JUnit

    Hi All,

    I'm getting the following exception when running a JUNIT test case:
    org.springframework.transaction.UnexpectedRollback Exception: Transaction rolled back because it has been marked as rollback-only

    The flow goes like this:


    Code:
            
    try{
            createAgency(null);
                fail();
            }
            catch(ServiceException e){
                // Ok - createAgency should throw exception because
                // The input parameter was null
            }
    I call a service that I expect it to throw an ServiceException, I catch the exception, but the spring throws UnexpectedRollbackException.

    Does anyone has an idea?

    The JUNIT class annotations are (with the 'at' char before them):
    Code:
    RunWith(SpringJUnit4ClassRunner.class)
    TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false)
    Transactional(rollbackFor=Throwable.class)
    The exception stack trace:

    Code:
    company.cms.service.exception.MissingMandatoryFieldException: name
        at company.cms.service.impl.AbstractService.checkMandatoryField(AbstractService.java:108)
        at company.cms.service.impl.AbstractService.checkMandatoryStringMaxLength(AbstractService.java:163)
        at company.cms.service.impl.AgencyServiceImpl.validate(AgencyServiceImpl.java:76)
        at company.cms.service.impl.AgencyServiceImpl.createAgency(AgencyServiceImpl.java:55)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
        at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
        at company.cms.interceptor.InvocationTimerInterceptor.measureTime(InvocationTimerInterceptor.java:20)
        at company.cms.interceptor.ServiceInvocationTimerInterceptor.takeTime(ServiceInvocationTimerInterceptor.java:17)
        at sun.reflect.GeneratedMethodAccessor71.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:627)
        at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:616)
        at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:64)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
        at $Proxy61.createAgency(Unknown Source)
        at company.cms.service.AgencyServiceTest.createAgency(AgencyServiceTest.java:76)
        at company.cms.service.AgencyServiceTest.createAgencyValidation(AgencyServiceTest.java:99)
        at company.cms.service.AgencyServiceTest.createAgencyValidationTest(AgencyServiceTest.java:89)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.springframework.test.context.junit4.SpringTestMethod.invoke(SpringTestMethod.java:163)
        at org.springframework.test.context.junit4.SpringMethodRoadie.runTestMethod(SpringMethodRoadie.java:233)
        at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:333)
        at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
        at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
        at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:142)
        at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
        at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
        at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
        at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
        at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
    DEBUG main company.cms.interceptor.ServiceInvocationTimerInterceptor - END Service - public abstract void company.cms.service.AgencyService.createAgency(company.cms.persistent.model.permission.User,company.cms.web.pages.admin.agency.model.AgencyModelObject) throws company.cms.service.exception.ServiceException took 15 miliseconds
    DEBUG main org.hibernate.transaction.JDBCTransaction - rollback
    DEBUG main org.hibernate.transaction.JDBCTransaction - re-enabling autocommit
    DEBUG main org.hibernate.transaction.JDBCTransaction - rolled back JDBC Connection
    WARN main org.springframework.test.context.TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener9b9a30] to process 'after' execution for test: method [public void company.cms.service.AgencyServiceTest.createAgencyValidationTest() throws company.cms.service.exception.ServiceException], instance [company.cms.service.AgencyServiceTest1a7f9f0], exception [null]
    org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:672)
        at org.springframework.test.context.transaction.TransactionalTestExecutionListener$TransactionContext.endTransaction(TransactionalTestExecutionListener.java:504)
        at org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:277)
        at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:170)
        at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:344)
        at org.springframework.test.context.junit4.SpringMethodRoadie.runAfters(SpringMethodRoadie.java:307)
        at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:338)
        at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
        at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
        at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:142)
        at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
        at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
        at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
        at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
        at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
    DEBUG main org.hibernate.transaction.JDBCTransaction - begin
    DEBUG main org.hibernate.transaction.JDBCTransaction - current autocommit status: true
    DEBUG main org.hibernate.transaction.JDBCTransaction - disabling autocommit
    Thanks all
    Alon

  • #2
    Do not use transactional context for this test or mark it for rollback explicitly:

    Code:
        @Test
        @NotTransactional
        public void createAgencyValidationTest() {
            try {
                createAgency(null);
                fail();
            }
            catch(ServiceException e){
                // Ok - createAgency should throw exception because
                // The input parameter was null
            }
        }
    Code:
        @Test
        @Rollback
        public void createAgencyValidationTest() {
            try {
                createAgency(null);
                fail();
            }
            catch(ServiceException e){
                // Ok - createAgency should throw exception because
                // The input parameter was null
            }
        }

    Comment


    • #3
      Works!

      This Rollback annotation did the job...

      Thanks!

      Comment


      • #4
        In container ?

        Is this UnexpectedRollbackException specific to testing ?

        What will be the behaviour if I deploy my application to a web container using the JTATransactionManager and the following flow gets executed ?

        User enters data on a web page.
        User hits the submit button.
        Thread hits the Service1 layer method (transaction required).
        Since there's no txn yet, one is started.

        Service1 method calls another a Service2 method (transaction required).
        NO new txn is created, but Service2 method joins existing txn.

        Then within Service2's method an exception is thrown which should cause a rollback.

        Since Service2's method is running within one and the same txn as Service1's method, this one-and-only txn will be marked for rollback, which is ok.

        The question is whether in the above "production" scenario we will also get an UnexpectedRollbackException ? I hope not ? I hope the transaction manager is then smart enough to realise that this one-and-only-one txn has been marked for rollback ?

        Am I right ?
        Please advice.
        Thanks,
        EDH

        Comment


        • #5
          UnexpectedRollbackException is not test specific. It is from spring-tx. The behaviour will be the same.

          It is spring tx implementation specific. Sertice1 and Service2 has own TransactionStatus (DefaultTransactionStatus), wich has LocalRollbackOnly flag. When Service2 throws exception, it's TransactionStatus is marked as local rollback only. Then PlatformTransactionManager marks underlined transaction for rolback. When Sertice1's method invocation is completed it's TransactionStatus is not marked as local rollback only. In this case PlatformTransactionManager tries to commit. transaction marked for rollback only => UnexpectedRollbackException exception.

          You can set breakpoint at AbstractPlatformTransactionManager.processRollback and AbstractPlatformTransactionManager.commit and debug them.

          The solution can be exception throwing from Sertice1's method too. Which will mark Sertice1's TransactionStatus as local rollback only too.
          Code:
          Service1
          
             // marked as transactional
             public void f1() {
                  try {
                      service2.f2();
                  } catch (Service2Exception ex) {
                      // do something and re-throw ex
                      do();
                      throw ex;
                      // or throw Service1 onw exception
                      throw new Service1Exception(...);
                  }
             }

          Comment


          • #6
            Thanks for pointing this out.

            So if I understand your explanation correctly then although service1.f1 and service2.f2 are running in the same txn, service1.f1 doesn't notice that this txn has been set to rollback and tries to commit.

            Isn't that making things more difficult than they are ?

            Why do we have then the notion of txn "REQUIRED" and "REQUIRES_NEW" ?

            I would have expected that marking a txn for rollback, because an exception was thrown, would mark the txn for rollback for all participants.

            Is the behaviour you describe the same as what would happen when running in an EJB environment with CMT txns ? I don't think so, or am I mistaken ?

            Thanks,
            EDH

            Comment


            • #7
              So if I understand your explanation correctly then although service1.f1 and service2.f2 are running in the same txn, service1.f1 doesn't notice that this txn has been set to rollback and tries to commit.
              yes

              Why do we have then the notion of txn "REQUIRED" and "REQUIRES_NEW" ?
              Decalarative transaction mamangement with transaction attributes works the same as in EJB, for example, in case of service1.f1, service2.f2, hibernate trasnaction manager the picture looks like:
              in case of f1 "REQUIRED", f2 "REQUIRED": f1 has own f1:TransactionStatus, f2 has own f1:TransactionStatus, but underlined hibernate transaction for both of them the same hbt:Trasnaction
              in case of f1 "REQUIRED", f2 "REQUIRES_NEW": f1 has own f1:TransactionStatus, and works with onw hbt1:Transaction, f2 has own f1:TransactionStatus, and works with onw hbt2:Trasnaction, i.e. methods participate in different transactions

              Is the behaviour you describe the same as what would happen when running in an EJB environment with CMT txns ? I don't think so, or am I mistaken ?
              I haven't worked with EJB for a long time, as I remember container does rollback.

              I don't know why "LocalRollbackOnly" concept is needed, I guess spring developers can explain it better, it is internal implementation. I analized some classes: TransactionInterceptor (invoke), AbstractPlatformTransactionManager (getTransaction/doGetTransaction/handleExistingTransaction/commit/rollback), TransactionSynchronizationManager to figure out how it works. Three are three approaches how too deal with it:
              - via exception throwing
              - via programmatical transaction management for f1
              Code:
                      transactionTemplate.execute(new TransactionCallback() {
                          public Object doInTransaction(final TransactionStatus status) {
                              try {
                                  service2.f2();
                              } catch (Service2Exception ex) {
                                  status.setRollbackOnly();
                                  // do something 
                              }
                          }
                      });
              - trying to mark Service1.f1's TransactionStatus for rollback. It is possible solution, is it reasonable or not it is another question.
              Code:
                      try {
                          service2.f2();
                      } catch (Service2Exception ex) {
                          TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                          // do something 
                      }

              Comment


              • #8
                UnexpectedRollbackException

                Regarding two methods running in a different txn.

                For example:
                Code:
                public class Service1{
                  XyzDao xyzDao;
                  Service2 service2;
                  public void method1(Xyz xyz){
                       getXyzDao().create(xyz);
                       //Do some other stuff like instantiating an Abc abc = new Abc()
                       getService2().method2(abc);
                  }
                }
                
                public class Service2{
                  AbcDao abcDao;
                  public void method2(Abc abc){
                       getAbcDao().create(abc);
                  }
                }
                Service1.method1 = txn "REQUIRED".
                Service2.method2 = txn "REQUIRES_NEW".
                Service2.method2 throws a DataIntegrityViolationException because I'm trying to save something with a missing not-null column.

                In the debug output I can see a new txn and a new Session being created.
                I can also see that this first txn is getting suspended and another txn and session being created. That's fine.

                I can see in the debug output that my second txn fails and is marked for rollback, which is as expected because a problem occurred while persisting abc and therefore a DataIntegrityViolation was thrown.

                I can see in the debug output that my first txn which was suspended is resumed.

                But then what I really don't understand is why this first resumed txn is also considered failed and marked for rollback even after adding a catch clause in Service1.method1 ?

                Is this logical behaviour ?
                I would have expected the first txn not to be marked for rollback, only the second, independent txn. Why else marking Service2.method2 as "REQUIRES_NEW" ?

                What's going on here ?

                Thanks,
                EDH
                Last edited by EdwinDhondt; Feb 2nd, 2010, 06:42 AM.

                Comment


                • #9
                  I would have expected the first txn not to be marked for rollback, only the second, independent txn.
                  It behaves in such manner.

                  DataIntegrityViolation is runtime exception. That is why it is propagated above service1.method1, and as a result the first transaction is rolled back too. Add try/catch to method1:

                  Code:
                  public class Service1{
                    XyzDao xyzDao;
                    Service2 service2;
                    public void method1(Xyz xyz){
                         getXyzDao().create(xyz);
                         //Do some other stuff like instantiating an Abc abc = new Abc()
                         try {
                              getService2().method2(abc);
                         } catch (DataIntegrityViolationException ex) { // or catch DataAccessException
                            // handle exception without throwing runtime exception above
                         }
                    }
                  }
                  
                  public class Service2{
                    AbcDao abcDao;
                    public void method2(Abc abc){
                         getAbcDao().create(abc);
                    }
                  }

                  Comment


                  • #10
                    It's not working like that for me.
                    I've added a try-catch(Exception e) around the method call but still my first txn is getting set to rollback too.

                    Below is the stacktrace at the time I'm reaching the breakpoint within the catch:

                    Code:
                    2010-02-02 16:06:50,609 DEBUG [org.hibernate.impl.SessionImpl] - opened session at timestamp: 5181944670654464
                    2010-02-02 16:06:50,609 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Opened new Session [org.hibernate.impl.SessionImpl@9aa95c] for Hibernate transaction
                    2010-02-02 16:06:50,609 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Preparing JDBC Connection of Hibernate Session [org.hibernate.impl.SessionImpl@9aa95c]
                    2010-02-02 16:06:50,609 DEBUG [org.hibernate.jdbc.ConnectionManager] - opening JDBC connection
                    2010-02-02 16:06:50,820 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Changing isolation level of JDBC Connection [jdbc:oracle:thin:@VM-IDE-01:1521:XE, UserName=LED_D, Oracle JDBC driver] to 2
                    2010-02-02 16:06:50,820 DEBUG [org.hibernate.transaction.JDBCTransaction] - begin
                    2010-02-02 16:06:50,820 DEBUG [org.hibernate.transaction.JDBCTransaction] - current autocommit status: true
                    2010-02-02 16:06:50,820 DEBUG [org.hibernate.transaction.JDBCTransaction] - disabling autocommit
                    2010-02-02 16:06:50,820 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Exposing Hibernate transaction as JDBC transaction [jdbc:oracle:thin:@VM-IDE-01:1521:XE, UserName=LED_D, Oracle JDBC driver]
                    2010-02-02 16:06:52,382 DEBUG [org.springframework.orm.hibernate3.HibernateTemplate] - Found thread-bound Session for HibernateTemplate
                    2010-02-02 16:06:52,382 DEBUG [org.hibernate.event.def.AbstractSaveEventListener] - generated identifier: 3e21a601-df53-40f8-9d75-580a5d1963d0, using strategy: org.hibernate.id.Assigned
                    2010-02-02 16:06:52,382 DEBUG [org.springframework.orm.hibernate3.HibernateTemplate] - Not closing pre-bound Hibernate Session after HibernateTemplate
                    2010-02-02 16:06:56,448 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Initiating transaction rollback
                    2010-02-02 16:06:56,448 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Rolling back Hibernate transaction on Session [org.hibernate.impl.SessionImpl@9aa95c]
                    2010-02-02 16:06:56,448 DEBUG [org.hibernate.transaction.JDBCTransaction] - rollback
                    2010-02-02 16:06:56,448 DEBUG [org.hibernate.transaction.JDBCTransaction] - re-enabling autocommit
                    2010-02-02 16:06:56,448 DEBUG [org.hibernate.transaction.JDBCTransaction] - rolled back JDBC Connection
                    2010-02-02 16:06:56,448 DEBUG [org.hibernate.jdbc.ConnectionManager] - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
                    2010-02-02 16:06:56,448 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Resetting isolation level of JDBC Connection [jdbc:oracle:thin:@VM-IDE-01:1521:XE, UserName=LED_D, Oracle JDBC driver] to 2
                    2010-02-02 16:06:56,448 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Closing Hibernate Session [org.hibernate.impl.SessionImpl@9aa95c] after transaction
                    2010-02-02 16:06:56,448 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - Closing Hibernate Session
                    2010-02-02 16:06:56,448 DEBUG [org.hibernate.jdbc.ConnectionManager] - releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
                    2010-02-02 16:06:56,488 DEBUG [org.hibernate.jdbc.ConnectionManager] - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
                    2010-02-02 16:06:56,488 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Resuming suspended transaction
                    2010-02-02 16:06:56,488 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Participating transaction failed - marking existing transaction as rollback-only
                    2010-02-02 16:06:56,488 DEBUG [org.springframework.orm.hibernate3.HibernateTransactionManager] - Setting Hibernate transaction on Session [org.hibernate.impl.SessionImpl@a8a271] rollback-only
                    The last debug line refers to the very first txn and session (which I don't want to be set rollback-only.

                    Do you have any idea what I might be missing ?
                    Its driving me crazy ;-)

                    Comment


                    • #11
                      Try to debug TransactionInterceptor.invoke, or configure log4j trace level for it:

                      log4j.properties
                      Code:
                      log4j.logger.org.springframework.transaction = debug, console
                      log4j.logger.org.springframework.transaction.interceptor = trace, console
                      
                      log4j.additivity.org.springframework.transaction = false
                      log4j.additivity.org.springframework.transaction.interceptor = false

                      Comment


                      • #12
                        I've increased the log level as per your instructions.

                        I've attached the output.

                        I've spent hours on this, and I can't seem to pinpoint the cause.

                        I hope that you can see something odd occurring, because I really can't.

                        Any advice is welcome.

                        Thanks,
                        EDH
                        PS. My service classes are made of an interface, e.g. GebruikerService, and a corresponding implementation, e.g. GebruikerServiceImpl, that implements the interface.

                        Comment


                        • #13
                          I found it.
                          There was something wrong in my declarative txn demarcation rules.
                          grrrr

                          Comment

                          Working...
                          X