Announcement Announcement Module
Collapse
No announcement yet.
Spring Nested Transaction Rollback on Exception with Annotations Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring Nested Transaction Rollback on Exception with Annotations

    Hi,

    i try to get a
    simple
    nested rollback working, that if one submethod fails, all changes are rolled back which have been made in all preceding submethods . In my JUnitTest Class it performs as expected if i annote the unit test method as transactional, but not if i only declare the classA-methodB as transactional as i think this would be the right thing to do. Pseudo Code of what i am trying to do:

    Code:
    @ContextConfiguration(locations = { "classpath:context.xml" })
    ClassStart{
    	classACall(){
    		beanOfclassA = context.getBean(classA)
    		beanOfclassA.methodB
    	}
    }
    
    class A{
    	@Transactional(read-only=false,propagation=Propagation.NESTED,rollbackFor=Exception)
    	methodB{
    		try
    			call methodC
    			call methodD
    		catch exception
    
    		methodC throws exception
    			some jdbc calls
    
    		methodD throws exception
    			some jdbc calls
    	}
    }
    
    @RunWith(SpringJUnit4ClassRunner.class)
    UnitTestClass{
    
    @Test
    @Transactional(read-only=false,propagation=Propagation.NESTED,rollbackFor=Exception)
    testClassA()
    	ClassStart s = new ClassStart(),
    	s.classACall()
    }
    context.xml:

    Code:
    <beans 	xmlns="http://www.springframework.org/schema/beans"
    		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    		xmlns:context="http://www.springframework.org/schema/context"
        	xmlns:aop="http://www.springframework.org/schema/aop"
        	xmlns:tx="http://www.springframework.org/schema/tx"
         	xsi:schemaLocation="http://www.springframework.org/schema/beans 
         						http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         						http://www.springframework.org/schema/tx
         						http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
         						http://www.springframework.org/schema/context
    					        http://www.springframework.org/schema/context/spring-context-3.0.xsd
    					        http://www.springframework.org/schema/aop
    					        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
    	<bean id="dataSource_mysql" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
    		<property name="url" value="${jdbc_mysql.url}" />
    		<property name="username" value="${jdbc_mysql.username}" />
    		<property name="password" value="${jdbc_mysql.password}" />
    		<property name="initialSize" value="5" />
    		<property name="maxActive" value="50" />
    	</bean>
    	
    	<bean id="jdbcTemplate_mysql" class="org.springframework.jdbc.core.JdbcTemplate" autowire="byName">
    		<property name="dataSource" ref="dataSource_mysql" />
    	</bean>
    	
    	<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
    	
    	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      		<property name="dataSource" ref="dataSource_mysql"/>
    	</bean>
    I also tried the tx approach by using:
    Code:
    <tx:annotation-driven/>
    	
    	<tx:advice id="txAdvice" transaction-manager="transactionManager">
    		<tx:attributes>
    			<tx:method name="_mergeContacts" read-only="false" propagation="NESTED" rollback-for="MergeException"/>
    		</tx:attributes>
    	</tx:advice>
    But this didnïŋ―t work at all.

    So i got stuck and also confused about the correct usage.

    Also i am wondering why the JUnit Test works out of the box by just annotating the test method(even if i do not annotate anything in the class under test named classA)

    Maybe one of you can help me out on this.

    Some more questions:
    On the reference documentation there are two ways:
    the declarative approach and the annotation based.
    if i use the declarative, by using tx:annotation-driven and txAdvice - is it correct that i do not need a @transactional annotation at all?
    do i always need an aop:config on the tx:Advice?

    on the annotation based approach, there is no need for tx:advice? only tx:annotation-driven?

    Really thank you in advance for helping me on the way

    cheers

  • #2
    For starters I suggest a read on what a NESTED transaction is... It isn't what you think it is next to that MySQL doesn't support it. What you want is normal transactional behavior. Next to that spring uses proxies for aop (and transactions are applied using aop) so the internal method calls aren't proxies and any @Transactional setting on that isn't doing anything.

    I don't get your messing around with classes to enable testing... The rollback in the unit test works because that is the default if you use spring test framework. Don't catch exceptions, if you catch exceptions and swallow or handle them yourself transactions get committed.

    Also in your configuration I see nowhere you configuration of the bean you are using and which should be transactional. So please post some actual code and configuration in stead of hacking around in pseudo code.

    Comment


    • #3
      For starters I suggest a read on what a NESTED transaction is... It isn't what you think it is next to that MySQL doesn't support it. What you want is normal transactional behavior. Next to that spring uses proxies for aop (and transactions are applied using aop) so the internal method calls aren't proxies and any @Transactional setting on that isn't doing anything.

      I don't get your messing around with classes to enable testing... The rollback in the unit test works because that is the default if you use spring test framework. Don't catch exceptions, if you catch exceptions and swallow or handle them yourself transactions get committed.

      Also in your configuration I see nowhere you configuration of the bean you are using and which should be transactional. So please post some actual code and configuration in stead of hacking around in pseudo code.

      Comment


      • #4
        Maybe i am mislead on nested transaction so let me figure this out

        1)
        Maybe i got it clearer:
        As written at http://docs.oracle.com/cd/E17076_02/...nestedtxn.html
        nested transactions are supposed to cancel all child transactions on cancelling the parent transaction and not cancelling the parent transaction if a child has an error. but that has nothing to do with my normal transactional behaviour i want to achieve, as you mentioned before.
        Did i got it?

        2)As MySql doesnīt support nested transactions by 12/2011 -> my post is obsolete and would also be because itīs not the thing i actually want.

        3)Hopefully i can get an idea of how to get my normal transactional behaviour working, hopefully in the spring context, too.
        so i am using jdbctemplate for my mysql db calls and want to roll back all sql calls which are all part of one context.
        e.g updating id in this and that table.

        what is a good way of doing this perhaps in an annotional or declarative way using spring?

        Thank you for your time,

        cheers

        Comment


        • #5
          A nested transaction is basically a transaction which takes snapshots and you can rollback a part of a full transaction. As to date, if I'm not mistaken, only Oracle supports that notion of nested transactions.

          Regarding 3 that is basically what happens in a single transaction simply apply transactions on your service layer and at the method end it will either commit or rollback all changes to the database.

          Comment


          • #6
            can u please give me a hint/link on how to do that? i donīt have a clue after up and down reading the chapters in the spring reference documentation...
            at the moment i just defined the jdbc template bean and use it in my dao. nothing more.
            i am familiar with savepoints and rollback in the old sql connector fashion but not in spring.
            there is the transactionmanager of spring, do i need to use the transaction template or is there a more convinient not so invasive way to implement this - because as i read i have to wrap the transaction template all around every sql calls and they are a bunch...
            thats why i come across the nested transcations, seemed seductive, to just annote them nested and all the magic happens

            Comment


            • #7
              Originally posted by Marten Deinum
              simply apply transactions on your service layer and at the method end it will either commit or rollback all changes to the database.
              There is nothing more to it than that... Simply put @Transactional on your service and that is all... Everything happens in a single transactions and either succeeds or fails... No need for nested transactions (they serve a different need).

              I strongly suggest you read the transactional chapter again and try to understand it better. As a hint the transactional layer should be the service layer NOT the dao layer (they participate in the already started transaction).

              Comment


              • #8
                I now tried everything and got a little bit further, but still the rollback does not happen. The log4j Trace tells me spring is doing something:

                Code:
                18:22:57 DEBUG [main] NameMatchTransactionAttributeSource:94 Adding transactional method [_mergeContacts] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-MergeException]
                18:22:57 DEBUG [main] DefaultListableBeanFactory:244 Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:244 Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:458 Finished creating instance of bean '(inner bean)'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:1461 Invoking afterPropertiesSet() on bean with name 'txAdvice'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:458 Finished creating instance of bean 'txAdvice'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:244 Returning cached instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:244 Returning cached instance of singleton bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:244 Returning cached instance of singleton bean 'dataSource_mysql'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:214 Creating shared instance of singleton bean 'jdbcTemplate_mysql'
                18:22:57 DEBUG [main] DefaultListableBeanFactory:430 Creating instance of bean 'jdbcTemplate_mysql'
                but nothing about a rollback and in the db all has been committed.

                My last context.xml goes as follows:
                Code:
                <context:property-placeholder location="file:server.properties" />
                	<context:component-scan base-package="server" />
                	
                	<tx:advice id="txAdvice" transaction-manager="transactionManager">
                		<tx:attributes>
                			<tx:method name="_mergeContacts" read-only="false" rollback-for="MergeException"/>
                		</tx:attributes>
                	</tx:advice>
                
                	<aop:aspectj-autoproxy/>
                	<aop:config>
                        <aop:pointcut id="serverObjectOperation" expression="execution(* server.ServerObject._merge*(..))"/>
                        <aop:advisor advice-ref="txAdvice" pointcut-ref="serverObjectOperation"/>
                    </aop:config>
                	
                	<bean id="myService" class="server.MyService"/>
                	
                	<bean id="mergeDao" class="server.dao.MergeDao">
                		<property name="jdbcTemplate" ref="jdbcTemplate_mysql" />
                	</bean>
                	
                	<bean id="dataSource_mysql" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
                		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
                		<property name="url" value="${jdbc_mysql.url}" />
                		<property name="username" value="${jdbc_mysql.username}" />
                		<property name="password" value="${jdbc_mysql.password}" />
                		<property name="initialSize" value="5" />
                		<property name="maxActive" value="50" />
                	</bean>
                	
                	<bean id="jdbcTemplate_mysql" class="org.springframework.jdbc.core.JdbcTemplate" autowire="byName">
                		<property name="dataSource" ref="dataSource_mysql" />
                	</bean>
                	
                	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                  		<property name="dataSource" ref="dataSource_mysql"/>
                	</bean>
                and my classes are:
                Code:
                /-----------------------------------------------------------------------------------------------------------------
                	package server;
                	public class MyService{
                	              XMLAppContext appContext = new XMLAppContext("context.xml");
                
                		@Transactional(readOnly = false, rollbackFor=MergeException.class)
                		public String _mergeContacts(String id1, String id2) throws MergeException {
                				MergeDao m = (MergeDao) appContext.getBean("mergeDao");
                				return m._mergeContacts(id1,id2);
                		}
                	}
                	
                	/-----------------------------------------------------------------------------------------------------------------
                	package server.dao;
                	public class MergeDao{
                		@Autowired
                		JdbcTemplate jdbcTemplate_mysql;
                		
                		public String _mergeContacts(String id1, String id2) throws MergeException{
                			String newID = generateNewID();
                			jdbcTemplate_mysql.update("INSERT INTO XXX", newID,id1,id2);
                			
                			replaceReferences(id1,newID);
                			replaceReferences(id2,newID);
                                                          if(true)
                                                                 throw new MergeException();
                		}
                		
                		private void replaceReferences(String id1, String id2) throws MergeException{
                			jdbcTemplate_mysql.update("Update table_XXX", id2,id1,);
                		}
                	}
                I know that it is made double: tx:advice with this method and @Transactional on the method itself
                As i (think i) know i can either use tx:annotation-driven with @Transactional above the method or tx:advice with pointcut and aop:aspectj-autoproxy.
                As a third option, maybe, i found context:component-scan with scans the package for annotations.

                Lost in code and options, trying hundred of different mixes, i finally have to wave the flag and cry for help ;(

                Comment


                • #9
                  component-scan has NOTHING to do with @Transactional it has to do with @Component... In that same regard aspectj-autoproxy has nothing to do with aop:config, aspectj-autoproxy is for applying @Aspect annotated beans to classes.

                  Your code is flawed NEVER recreate an application context as you do in your MyService class that will lead to duplicate bean instances and not properly managing transactions. You should auto wire your dao into your service class, also spring will only apply transactions (actual create proxies and apply op in general) on beans it controls if you create an instance of your MyService bean outside of spring this isn't going to be managed!

                  On a final note you are using MySQL make sure that you use transactional tables, InnoDB typed tables, you wouldn't be the first to use non-transactional tables (MyISAM) and expect rollbacks.

                  On a really final note if possible zip your code and post here (only code, xml no jars preferably a mvn/gradle/ant+ivy project), that would make it easier to help you.

                  Comment


                  • #10
                    Success!

                    Thank you, i finally got it working!
                    The mistake was in fact as you told me, my MyService Class was not under Spring control. So I moved up the appContext into the main and autowired all my dao beans in MyService class and in the end, the magic happened as wanted

                    For completeness my final "learning" eclipse java project is attached as zip(Jars not included - see ".classpath" file for all necessary libs).

                    I am really happy now, merry X-Mas!

                    Comment

                    Working...
                    X