Announcement Announcement Module
Collapse
No announcement yet.
@Transactional methods simulated with inner class Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • @Transactional methods simulated with inner class

    I'm sure this is a newbie question, but I have a few methods in a web controller that I want to make transactional, but I don't want to make the entire controller transactional (some persistence methods have after effects that involve the database but should not be involved in the main transaction). I don't want to make a separate DAO service for each controller (or at least I want to keep the DAO with the controller).

    Since the AOP proxy is on the entire controller, internal method invocations will not see the @Transactional annotation if it on a method. From the spring docs:
    Note: Since this mechanism is based on proxies, only 'external' method calls coming in through the proxy will be intercepted. This means that 'self-invocation', i.e. a method within the target object calling some other method of the target object, won't lead to an actual transaction at runtime even if the invoked method is marked with @Transactional!
    For example, the persist method won't actually be transactional in this example because it is called from createNode() which is inside the controller.
    Code:
    public class MyWebController extends Controller {
    	public ModelAndView createNode(HttpServletRequest request, HttpServletResponse response) {
    	 Node node = buildNode(request);
             persist(node)
             activeNode(node);
    	}
    
    	@Transactional
    	public void persist(Node n) {
    		...
    	}; 
    }
    Q: Can I use an @Transactional inner class?
    Code:
    public class MyWebController extends Controller {
    	public ModelAndView createNode(HttpServletRequest request, HttpServletResponse response) {
    	 Node node = buildNode(request);
             myMiniDAO.persist(node)
             activeNode(node);
    	}
    
    	@Transactional
            private class MiniDAO {
    		public void persist(Node n) {
    			...
    		}; 
            }
    }
    Q: Is there any way to achieve this without putting all of my persistence methods in their own class. The above example seems like kind of an anti-pattern. Is there an easier way?
    Last edited by honeybunny; Nov 21st, 2008, 01:28 PM. Reason: removed dumb question

  • #2
    Originally posted by honeybunny View Post
    The question that springs to mind, is why even allow the @Transactional annotation on a method? Do some other transaction managers allow this to work as expected, or am I misunderstanding the meaning of Object in the docs (does it extend to methods)?
    @Transactional can be applied to the method because it's convenient. You may want to have only particular class methods to be transactional.

    Originally posted by honeybunny View Post
    Do some other transaction managers allow this to work as expected, or am I misunderstanding the meaning of Object in the docs (does it extend to methods)?
    It's not clear what do you mean under 'other transaction managers'. @Transactional is the facility offered by Spring Framework, that, in turn can integrate with JTA transaction manager.

    About the documentation - it means exactly the situation you described above, i.e. when one method calls @Transactional method on the same instance (i.e. on 'this' reference).

    Originally posted by honeybunny View Post
    Q: Can I use an @Transactional inner class?
    It's very important to understand general principles that are used by spring. We can separate different levels at the framework and define their interdependencies. Let's restrict ourself with 'transactional' layer here.

    So, the lowest level is IoC. It's big, it's flexible, it's cool, it doesn't relate directly to transactions.

    Next layer is AOP. AOP used to be built under IoC in order to return aspect-aware proxies instead of 'raw' beans from IoC-managed context. AOP supports aspectj weaving since spring 2, so, it's possible to inject aspects logic directly to the byte code and avoid using proxies then.

    Transactional layer is built on AOP. I.e. special transaction-related logic is implemented and injected to the beans using AOP layer. Hence, the general principle is that transactional logic may be injected everywhere aspects logic can be injected.

    Let's look to your question - it can be transformed to the following then: 'is it possible to inject aspects to the non-static inner class?'. The answer is 'it depends' Let's look through the options:
    • it's possible to have inner bean transactional if it's used only via interfaces and jdk-proxying is used;
    • it's not possible to add transactional semantic to the inner class instance if cglib proxies are used because cglib requires proxied class to have a no-args constructor but inner classes are compiled to have an implicit constructor that receives single argument - reference to the enclosing class object. Hence, cglib proxy for such class can't be created;
    • It's possible to inject transactional logic via aspectj, i.e. inject the logic directly to the bytecode;

    The last approach is the most powerful and the most complex. I think it's better to introduce an example about that, so, check the end of the post for it.

    Originally posted by honeybunny View Post
    Q: Is there any way to achieve this without putting all of my persistence methods in their own class. The above example seems like kind of an anti-pattern. Is there an easier way?
    The answer is based on the same logic as above - aspect logic can't be applied if the proxied target bean calls the method on itself - the proxy just doesn't have a chance to intercept the call. However, if aop logic (transactional logic in our case) is injected to the bytecode everything is ok.

    A lot of words, let's see a practical example. Here is an inner class which is marked by @Transactional and we expect it's method to be executed with transactional semantics. Another significant point is that self-invocation is advised. Also you can see that ordinary aspects are introduced and their logic is applied as well:

    AopService.java

    Code:
    package com.spring.aop;
    
    import org.springframework.transaction.annotation.Transactional;
    
    public class AopService {
    
        @Transactional
        public class InnerService {
            public void innerMethod() {
                System.out.println("xxx: AopService$InnerClass.innerMethod()");
            }
        }
    
        public void outerMethod() {
            System.out.println("xxx: AopService.outerMethod()");
            transactionalMethod();
        }
    
        @Transactional
        private void transactionalMethod() {
            System.out.println("xxx: AopService.transactionalMethod()");
        }
    }
    TestAspect.java

    Code:
    package com.spring.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class TestAspect {
    
        @Around("execution(* com.spring.aop.AopService.*(..)) || execution(* com.spring.aop.AopService..*.*(..))")
        public Object advice(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("aaa: TestAspect.advice(): " + joinPoint);
            return joinPoint.proceed();
        }
    
    }
    SpringStart.java

    Code:
    package com.spring;
    
    import com.spring.aop.AopService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class SpringStart {
    	public static void main(String[] args) throws Exception {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
    
            System.out.println("");
            System.out.println("");
            System.out.println("-------------------> Transacted inner bean example <-------------------");
            AopService.InnerService inner = (AopService.InnerService) context.getBean("inner");
            inner.innerMethod();
    
    
            System.out.println("");
            System.out.println("");
            System.out.println("-------------------> Transacted self-invocation example <-------------------");
            AopService outer = (AopService) context.getBean("outer");
            outer.outerMethod();
        }
    }
    spring-config.xml

    HTML Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
      http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd">
    
    	<context:component-scan base-package="com.spring.aop"/>
    	<context:load-time-weaver/>
    
        <bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf">
            <property name="transactionManager" ref="transactionManager"/>
        </bean>
    
        <bean id="outer" class="com.spring.aop.AopService"/>
        <bean id="inner" class="com.spring.aop.AopService$InnerService">
            <constructor-arg ref="outer"/>
        </bean>
    
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource">
                <bean class="com.spring.tx.util.DummyDataSource"/>
            </property>
        </bean>
    
    </beans>
    aop.xml

    HTML Code:
    <!DOCTYPE aspectj PUBLIC
            "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
    <aspectj>
    
        <weaver options="-verbose -showWeaveInfo"/>
    
        <aspects>
            <aspect name="com.spring.aop.TestAspect"/>        
        </aspects>
    
      </aspectj>
    Also it's worth to turn on spring logging by putting the following file at classpath:

    log4j.xml

    HTML Code:
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    	<appender name="console" class="org.apache.log4j.ConsoleAppender">
    		<param name="Target" value="System.out"/>
    		<layout class="org.apache.log4j.PatternLayout">
    			<param name="ConversionPattern" value="%-5p %c{1} - %m%n"/>
    		</layout>
    	</appender>
    
    	<root>
    		<priority value="debug"/>
    		<appender-ref ref="console"/>
    	</root>
    
    </log4j:configuration>
    Output (significant part):

    // Just discovered that max message size at the spring forum is 10000 symbols Find log at the message that follows this one.
    Also note that I used utility class DummyDataSource - it's just a dumb DataSource implementation I created for the testing needs. Feel free to define transaction manager and related resources as necessary.

    If you have questions about the sample above I suggest to read the following:

    6.8.4. Load-time weaving with AspectJ in the Spring Framework
    9.5.9. Using @Transactional with AspectJ

    Also note that it's necessary to prepare environment as described at the 'Load-time weaving with AspectJ' section, i.e. put necessary jars at classpath and start the test with defined javaagent.

    The last note is that it's possible to use not only load-time aspectj weaving but compile-time weaving as well.

    That's all

    Comment


    • #3
      The output:

      -------------------> Transacted inner bean example <-------------------
      DEBUG org.springframework.beans.factory.support.Abstract BeanFactory:214 - Returning cached instance of singleton bean 'inner'
      aaa: TestAspect.advice(): execution(void com.spring.aop.AopService.InnerService.innerMethod ())
      DEBUG org.springframework.transaction.interceptor.Abstra ctFallbackTransactionAttributeSource:107 - Adding transactional method [innerMethod] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
      DEBUG org.springframework.transaction.support.AbstractPl atformTransactionManager:371 - Creating new transaction with name [com.spring.aop.AopService$InnerService.innerMethod]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
      DEBUG org.springframework.jdbc.datasource.DataSourceTran sactionManager:202 - Acquired Connection [com.spring.tx.util.DummyDataSource$1@5db5ae] for JDBC transaction
      xxx: AopService$InnerClass.innerMethod()
      DEBUG org.springframework.transaction.support.AbstractPl atformTransactionManager:730 - Initiating transaction commit
      DEBUG org.springframework.jdbc.datasource.DataSourceTran sactionManager:259 - Committing JDBC transaction on Connection [com.spring.tx.util.DummyDataSource$1@5db5ae]
      DEBUG org.springframework.jdbc.datasource.DataSourceTran sactionManager:314 - Releasing JDBC Connection [com.spring.tx.util.DummyDataSource$1@5db5ae] after transaction
      DEBUG org.springframework.jdbc.datasource.DataSourceUtil s:312 - Returning JDBC Connection to DataSource


      -------------------> Transacted self-invocation example <-------------------
      DEBUG org.springframework.beans.factory.support.Abstract BeanFactory:214 - Returning cached instance of singleton bean 'outer'
      aaa: TestAspect.advice(): execution(void com.spring.aop.AopService.outerMethod())
      xxx: AopService.outerMethod()
      aaa: TestAspect.advice(): execution(void com.spring.aop.AopService.transactionalMethod())
      DEBUG org.springframework.transaction.interceptor.Abstra ctFallbackTransactionAttributeSource:107 - Adding transactional method [transactionalMethod] with attribute [PROPAGATION_REQUIRED,ISOLATION_DEFAULT]
      DEBUG org.springframework.transaction.support.AbstractPl atformTransactionManager:371 - Creating new transaction with name [com.spring.aop.AopService.transactionalMethod]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
      DEBUG org.springframework.jdbc.datasource.DataSourceTran sactionManager:202 - Acquired Connection [com.spring.tx.util.DummyDataSource$1@1c794cc] for JDBC transaction
      xxx: AopService.transactionalMethod()
      DEBUG org.springframework.transaction.support.AbstractPl atformTransactionManager:730 - Initiating transaction commit
      DEBUG org.springframework.jdbc.datasource.DataSourceTran sactionManager:259 - Committing JDBC transaction on Connection [com.spring.tx.util.DummyDataSource$1@1c794cc]
      DEBUG org.springframework.jdbc.datasource.DataSourceTran sactionManager:314 - Releasing JDBC Connection [com.spring.tx.util.DummyDataSource$1@1c794cc] after transaction
      DEBUG org.springframework.jdbc.datasource.DataSourceUtil s:312 - Returning JDBC Connection to DataSource

      Comment

      Working...
      X