<bean id="clientManager" class="com.acme.service.impl.ClientManagerImpl"> <sec:intercept-methods access-decision-manager-ref="accessDecisionManager"> <sec:protect method="get" access="AFTER_ACL_READ"/> <sec:protect method="save" access="ROLE_ADMIN,ACL_WRITE"/> </sec:intercept-methods> <constructor-arg ref="clientDao"/> <property name="mutableAclService" ref="aclService"/></bean>
<aop:config> <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* *..service.*Manager.*(..))" order="0"/></aop:config><tx:advice id="txAdvice"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="save*" rollback-for="DuplicateNameException,UserExistsException" /> <tx:method name="*"/> </tx:attributes></tx:advice>
Regardless of the root cause, I found a reasonable workaround was to revert to one of the "old" (more verbose) methods of transaction demarcation. Here's what my final working configuration looks like:
<bean id="baseTransactionProxy" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager"/> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="save*">PROPAGATION_REQUIRED,-DuplicateNameException,-UserExistsException</prop> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property></bean><bean id="clientManager" parent="baseTransactionProxy"> <property name="target"> <bean class="com.acme.service.impl.ClientManagerImpl"> <sec:intercept-methods access-decision-manager-ref="accessDecisionManager"> <sec:protect method="get" access="AFTER_ACL_READ"/> <sec:protect method="save" access="ROLE_ADMIN,ACL_WRITE"/> </sec:intercept-methods> <constructor-arg ref="clientDao"/> <property name="mutableAclService" ref="aclService"/> </bean> </property></bean>