So I have started to use Springs funky new schema support and
AOP for applying transactions. Previously we were doing things the very long and hard way:
<bean name="myControllerTarget" class="uk....MyController">
</bean>
<bean name="/mycontroller/myurl.htm" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.springframework.web.servlet.mvc.Controller</value>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<value>myControllerTarget</value>
</list>
</property>
</bean>
(Let’s not go into the whole “transactional controller” debate :))
Anyway, whilst being very verbose, this had the very valuable benefit of being extremely explicit. Which, as we will see, is extremely useful quality.
So I decided that I would use Spring’s new schema and ended up with:
<tx:advice id="readWriteTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="handleRequest"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* org.springframework.web.servlet.mvc.Controller.*(..))"/>
<aop:advisor advice-ref="readWriteTxAdvice" pointcut-ref="serviceMethods"/>
</aop:config>
and everything stopped working :( More specifically, when I programatically rolled back a transaction I would end up with an UnexpectedRollbackException :( After asking the exceptionally helpful people at the Spring forum (http://forum.springframework.org/showthread.php?t=26574), it turned out that I had somehow managed to end up with a nested transaction….the nested transaction was marked as rollback whilst the outer transaction was trying to be committed.
So, off I go exercising my brain trying to determine how on earth I was getting a nested transaction and I thought “hmmm, the pointcut is Controller., maybe this is being applied to implementations of Controller”. Given that the AbstractController has a “handleRequest” and “handleRequestInternal” method, I assumed *yes, ass out of u and me ;) that one transaction was being started when handleRequest was invoked and another when handleRequestInternal. This is wrong wrong wrong for two reasons; Controller.* doesn’t match (although I need to confirm this) methods on subclasses and secondly the transaction advice explicity only handles the “handleRequest” method, but why should I let little things like that stop me :)
So I thought, “well, the pointcut is obviously too inclusive, lets restrict it” and ended up with
<tx:advice id="readWriteTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="handleRequest"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* org.springframework.web.servlet.mvc.Controller.handle(..))"/>
<aop:advisor advice-ref="readWriteTxAdvice" pointcut-ref="serviceMethods"/>
</aop:config>
Thats better isn’t it. Deployed, and it worked.
Then it struck me…..the method on the Controller is called “handleRequest”, not “handle”.....as a result the pointcut would match absolutely nothing :)
So how the flipping heck is this thing working, because rollbacks were working, commits were working etc.?
The other point to this joyful account is that fact that the context in which the aop was defined was importing another context which had a controller defined (actually it was the Spring Web Flow controller). And after looking in their, it was immediately apparent that the flow controller was wrapped using the old style.
Great.
So actually what was happening was the controller was coming out of the included context already wrapped in a transactional proxy. The aop: stuff in the “parent” context was then wrapping it in another transaction.
By “fixing” the aop:pointcut I had ineffect disabled the aop:pointcut, hence there was only one transactional proxy..the one explicitly defined in the included flow.
The fix was easy when I understand the problem.
I managed to spend a good couple of hours identifying the problem and the “solution” not by understanding the problem, but understanding its behaviour. The “solution” was identified not because it fixed the problem, but because it appeared to work.
This is actually quite rare for me…I am usually so anal that I absolutely have to understand why it is breaking…it usually isn’t enough to simply know it works, I have to know why :)
Ah well…..
Moral of the story…make sure you really understand the cause of the problem, and not just its characteristics…..and don’t believe everything I say about aop :)