From f4f3667679e7f7bb9feb784134fe411eca2c7acd Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 8 May 2009 23:13:43 +0000 Subject: [PATCH] @Transactional supports qualifier value for choosing between multiple transaction managers git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1134 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../Ejb3TransactionAnnotationParser.java | 10 +- .../SpringTransactionAnnotationParser.java | 3 +- .../transaction/annotation/Transactional.java | 11 +- .../AnnotationDrivenBeanDefinitionParser.java | 3 +- .../DefaultTransactionAttribute.java | 15 +- .../DelegatingTransactionAttribute.java | 4 + .../interceptor/TransactionAspectSupport.java | 134 ++++++++++++++---- .../interceptor/TransactionAttribute.java | 14 +- .../interceptor/TransactionInterceptor.java | 31 ++-- .../TransactionProxyFactoryBean.java | 8 +- .../config/AnnotationDrivenTests.java | 25 +++- .../config/TransactionalService.java | 14 +- .../annotationDrivenProxyTargetClassTests.xml | 11 +- 13 files changed, 208 insertions(+), 75 deletions(-) diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java index 74c25c38d5f..bb0f960a030 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Ejb3TransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,11 @@ package org.springframework.transaction.annotation; import java.io.Serializable; import java.lang.reflect.AnnotatedElement; - import javax.ejb.ApplicationException; import javax.ejb.TransactionAttributeType; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttribute; -import org.springframework.transaction.support.DefaultTransactionDefinition; /** * Strategy implementation for parsing EJB3's {@link javax.ejb.TransactionAttribute} @@ -53,8 +52,7 @@ public class Ejb3TransactionAnnotationParser implements TransactionAnnotationPar * EJB3-specific TransactionAttribute, implementing EJB3's rollback rules * which are based on annotated exceptions. */ - private static class Ejb3TransactionAttribute extends DefaultTransactionDefinition - implements TransactionAttribute { + private static class Ejb3TransactionAttribute extends DefaultTransactionAttribute { public Ejb3TransactionAttribute(TransactionAttributeType type) { setPropagationBehaviorName(PREFIX_PROPAGATION + type.name()); @@ -62,7 +60,7 @@ public class Ejb3TransactionAnnotationParser implements TransactionAnnotationPar public boolean rollbackOn(Throwable ex) { ApplicationException ann = ex.getClass().getAnnotation(ApplicationException.class); - return (ann != null ? ann.rollback() : (ex instanceof RuntimeException || ex instanceof Error)); + return (ann != null ? ann.rollback() : super.rollbackOn(ex)); } } diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java index 15262aefba6..6d18e3e0cbd 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -17,8 +17,8 @@ package org.springframework.transaction.annotation; import java.io.Serializable; -import java.lang.reflect.AnnotatedElement; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.util.ArrayList; import org.springframework.transaction.interceptor.NoRollbackRuleAttribute; @@ -58,6 +58,7 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP rbta.setIsolationLevel(ann.isolation().value()); rbta.setTimeout(ann.timeout()); rbta.setReadOnly(ann.readOnly()); + rbta.setQualifier(ann.value()); ArrayList rollBackRules = new ArrayList(); Class[] rbf = ann.rollbackFor(); for (Class rbRule : rbf) { diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Transactional.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Transactional.java index c0f452cc5d0..56211ab503d 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Transactional.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/annotation/Transactional.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2006 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,15 @@ import org.springframework.transaction.TransactionDefinition; @Documented public @interface Transactional { + /** + * A qualifier value for the specified transaction. + *

May be used to determine the target transaction manager, + * matching the qualifier value (or the bean name) of a specific + * {@link org.springframework.transaction.PlatformTransactionManager} + * bean definition. + */ + String value() default ""; + /** * The transaction propagation type. *

Defaults to {@link Propagation#REQUIRED}. diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java index b38aebff753..b189a2328c3 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java @@ -95,8 +95,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { private static void registerTransactionManager(Element element, BeanDefinition def) { String transactionManagerName = (element.hasAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE) ? element.getAttribute(TxNamespaceUtils.TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME); - def.getPropertyValues().addPropertyValue( - TxNamespaceUtils.TRANSACTION_MANAGER_PROPERTY, new RuntimeBeanReference(transactionManagerName)); + def.getPropertyValues().addPropertyValue("transactionManagerBeanName", transactionManagerName); } diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java index 6af4bd74a7b..c197ffc8946 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java @@ -27,6 +27,9 @@ import org.springframework.transaction.support.DefaultTransactionDefinition; */ public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute { + private String qualifier; + + /** * Create a new DefaultTransactionAttribute, with default settings. * Can be modified through bean property setters. @@ -66,10 +69,18 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im } + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } + + public String getQualifier() { + return this.qualifier; + } + /** - * Default behavior is as with EJB: rollback on unchecked exception. + * The default behavior is as with EJB: rollback on unchecked exception. * Additionally attempt to rollback on Error. - * Consistent with TransactionTemplate's behavior. + *

This is consistent with TransactionTemplate's default behavior. */ public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java index c6c05d5d5b9..9c895a5e37b 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java @@ -64,6 +64,10 @@ public abstract class DelegatingTransactionAttribute implements TransactionAttri return this.targetAttribute.getName(); } + public String getQualifier() { + return this.targetAttribute.getQualifier(); + } + public boolean rollbackOn(Throwable ex) { return this.targetAttribute.rollbackOn(ex); } diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java index a455ee7b1b5..bbe27688e2e 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -17,18 +17,30 @@ package org.springframework.transaction.interceptor; import java.lang.reflect.Method; +import java.util.Map; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.core.NamedThreadLocal; import org.springframework.transaction.NoTransactionException; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * Base class for transactional aspects, such as the AOP Alliance @@ -61,7 +73,7 @@ import org.springframework.util.ClassUtils; * @see #setTransactionAttributes * @see #setTransactionAttributeSource */ -public abstract class TransactionAspectSupport implements InitializingBean { +public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { // NOTE: This class must not implement Serializable because it serves as base // class for AspectJ aspects (which are not allowed to implement Serializable)! @@ -94,8 +106,9 @@ public abstract class TransactionAspectSupport implements InitializingBean { * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive() */ protected static TransactionInfo currentTransactionInfo() throws NoTransactionException { - return (TransactionInfo) transactionInfoHolder.get(); + return transactionInfoHolder.get(); } + /** * Return the transaction status of the current method invocation. * Mainly intended for code that wants to set the current transaction @@ -114,23 +127,31 @@ public abstract class TransactionAspectSupport implements InitializingBean { protected final Log logger = LogFactory.getLog(getClass()); - /** Delegate used to create, commit and rollback transactions */ + private String transactionManagerBeanName; + private PlatformTransactionManager transactionManager; - /** Helper used to find transaction attributes */ private TransactionAttributeSource transactionAttributeSource; + private BeanFactory beanFactory; + /** - * Set the transaction manager. This will perform actual - * transaction management: This class is just a way of invoking it. + * Specify the name of the default transaction manager bean. + */ + public void setTransactionManagerBeanName(String transactionManagerBeanName) { + this.transactionManagerBeanName = transactionManagerBeanName; + } + + /** + * Specify the target transaction manager. */ public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } /** - * Return the transaction manager. + * Return the transaction manager, if specified. */ public PlatformTransactionManager getTransactionManager() { return this.transactionManager; @@ -160,7 +181,6 @@ public abstract class TransactionAspectSupport implements InitializingBean { * @see CompositeTransactionAttributeSource * @see MethodMapTransactionAttributeSource * @see NameMatchTransactionAttributeSource - * @see AttributesTransactionAttributeSource * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource */ public void setTransactionAttributeSources(TransactionAttributeSource[] transactionAttributeSources) { @@ -174,7 +194,6 @@ public abstract class TransactionAspectSupport implements InitializingBean { * @see TransactionAttributeSourceEditor * @see MethodMapTransactionAttributeSource * @see NameMatchTransactionAttributeSource - * @see AttributesTransactionAttributeSource * @see org.springframework.transaction.annotation.AnnotationTransactionAttributeSource */ public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { @@ -188,22 +207,79 @@ public abstract class TransactionAspectSupport implements InitializingBean { return this.transactionAttributeSource; } + /** + * Set the BeanFactory to use for retrieving PlatformTransactionManager beans. + */ + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = beanFactory; + } /** * Check that required properties were set. */ public void afterPropertiesSet() { - if (getTransactionManager() == null) { - throw new IllegalArgumentException("Property 'transactionManager' is required"); + if (this.transactionManager == null && this.beanFactory == null) { + throw new IllegalStateException( + "Setting the property 'transactionManager' or running in a ListableBeanFactory is required"); } - if (getTransactionAttributeSource() == null) { - throw new IllegalArgumentException( + if (this.transactionAttributeSource == null) { + throw new IllegalStateException( "Either 'transactionAttributeSource' or 'transactionAttributes' is required: " + "If there are no transactional methods, then don't use a transaction aspect."); } } + /** + * Determine the specific transaction manager to use for the given transaction. + */ + protected PlatformTransactionManager determineTransactionManager(TransactionAttribute txAttr) { + if (this.transactionManager != null) { + return this.transactionManager; + } + PlatformTransactionManager chosen = null; + String qualifier = txAttr.getQualifier(); + if (StringUtils.hasLength(qualifier)) { + if (!(this.beanFactory instanceof ConfigurableListableBeanFactory)) { + throw new IllegalStateException("BeanFactory required to be a ConfigurableListableBeanFactory " + + "for resolution of qualifier '" + qualifier + "': " + this.beanFactory.getClass()); + } + ConfigurableListableBeanFactory bf = (ConfigurableListableBeanFactory) this.beanFactory; + Map tms = + BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, PlatformTransactionManager.class); + for (String beanName : tms.keySet()) { + if (bf.containsBeanDefinition(beanName)) { + BeanDefinition bd = bf.getBeanDefinition(beanName); + if (bd instanceof AbstractBeanDefinition) { + AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; + AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName()); + if ((candidate != null && qualifier.equals(candidate.getAttribute(AutowireCandidateQualifier.VALUE_KEY))) || + qualifier.equals(beanName) || ObjectUtils.containsElement(bf.getAliases(beanName), qualifier)) { + if (chosen != null) { + throw new IllegalStateException("No unique PlatformTransactionManager bean found " + + "for qualifier '" + qualifier + "'"); + } + chosen = tms.get(beanName); + } + } + } + } + } + if (chosen != null) { + return chosen; + } + else if (this.transactionManagerBeanName != null) { + return this.beanFactory.getBean(this.transactionManagerBeanName, PlatformTransactionManager.class); + } + else if (this.beanFactory instanceof ListableBeanFactory) { + return BeanFactoryUtils.beanOfTypeIncludingAncestors(((ListableBeanFactory) this.beanFactory), PlatformTransactionManager.class); + } + else { + throw new IllegalStateException( + "Cannot retrieve PlatformTransactionManager beans from non-listable BeanFactory: " + this.beanFactory); + } + } + /** * Create a transaction if necessary, based on the given method and class. *

Performs a default TransactionAttribute lookup for the given method. @@ -217,7 +293,8 @@ public abstract class TransactionAspectSupport implements InitializingBean { protected TransactionInfo createTransactionIfNecessary(Method method, Class targetClass) { // If the transaction attribute is null, the method is non-transactional. TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); - return createTransactionIfNecessary(txAttr, methodIdentification(method)); + PlatformTransactionManager tm = determineTransactionManager(txAttr); + return createTransactionIfNecessary(tm, txAttr, methodIdentification(method)); } /** @@ -245,7 +322,7 @@ public abstract class TransactionAspectSupport implements InitializingBean { * @see #getTransactionAttributeSource() */ protected TransactionInfo createTransactionIfNecessary( - TransactionAttribute txAttr, final String joinpointIdentification) { + PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) { // If no name specified, apply method identification as transaction name. if (txAttr != null && txAttr.getName() == null) { @@ -259,7 +336,6 @@ public abstract class TransactionAspectSupport implements InitializingBean { TransactionStatus status = null; if (txAttr != null) { - PlatformTransactionManager tm = getTransactionManager(); if (tm != null) { status = tm.getTransaction(txAttr); } @@ -270,7 +346,7 @@ public abstract class TransactionAspectSupport implements InitializingBean { } } } - return prepareTransactionInfo(txAttr, joinpointIdentification, status); + return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); } /** @@ -281,10 +357,10 @@ public abstract class TransactionAspectSupport implements InitializingBean { * @param status the TransactionStatus for the current transaction * @return the prepared TransactionInfo object */ - protected TransactionInfo prepareTransactionInfo( + protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm, TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) { - TransactionInfo txInfo = new TransactionInfo(txAttr, joinpointIdentification); + TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification); if (txAttr != null) { // We need a transaction for this method if (logger.isTraceEnabled()) { @@ -319,7 +395,7 @@ public abstract class TransactionAspectSupport implements InitializingBean { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); } - getTransactionManager().commit(txInfo.getTransactionStatus()); + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } } @@ -337,7 +413,7 @@ public abstract class TransactionAspectSupport implements InitializingBean { } if (txInfo.transactionAttribute.rollbackOn(ex)) { try { - getTransactionManager().rollback(txInfo.getTransactionStatus()); + txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by rollback exception", ex); @@ -357,7 +433,7 @@ public abstract class TransactionAspectSupport implements InitializingBean { // We don't roll back on this exception. // Will still roll back if TransactionStatus.isRollbackOnly() is true. try { - getTransactionManager().commit(txInfo.getTransactionStatus()); + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by commit exception", ex); @@ -392,7 +468,9 @@ public abstract class TransactionAspectSupport implements InitializingBean { * Opaque object used to hold Transaction information. Subclasses * must pass it back to methods on this class, but not see its internals. */ - protected class TransactionInfo { + protected final class TransactionInfo { + + private final PlatformTransactionManager transactionManager; private final TransactionAttribute transactionAttribute; @@ -402,11 +480,17 @@ public abstract class TransactionAspectSupport implements InitializingBean { private TransactionInfo oldTransactionInfo; - public TransactionInfo(TransactionAttribute transactionAttribute, String joinpointIdentification) { + public TransactionInfo(PlatformTransactionManager transactionManager, + TransactionAttribute transactionAttribute, String joinpointIdentification) { + this.transactionManager = transactionManager; this.transactionAttribute = transactionAttribute; this.joinpointIdentification = joinpointIdentification; } + public PlatformTransactionManager getTransactionManager() { + return this.transactionManager; + } + public TransactionAttribute getTransactionAttribute() { return this.transactionAttribute; } @@ -438,7 +522,7 @@ public abstract class TransactionAspectSupport implements InitializingBean { private void bindToThread() { // Expose current TransactionStatus, preserving any existing TransactionStatus // for restoration after this transaction is complete. - this.oldTransactionInfo = (TransactionInfo) transactionInfoHolder.get(); + this.oldTransactionInfo = transactionInfoHolder.get(); transactionInfoHolder.set(this); } diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java index 6be460614a5..754f2ef0231 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java @@ -1,12 +1,12 @@ /* - * Copyright 2002-2005 the original author or authors. - * + * Copyright 2002-2009 the original author or authors. + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,7 +19,7 @@ package org.springframework.transaction.interceptor; import org.springframework.transaction.TransactionDefinition; /** - * This interface adds a rollbackOn specification to TransactionDefinition. + * This interface adds a rollbackOn specification to {@link TransactionDefinition}. * As custom rollbackOn is only possible with AOP, this class resides * in the AOP transaction package. * @@ -29,7 +29,9 @@ import org.springframework.transaction.TransactionDefinition; * @see RuleBasedTransactionAttribute */ public interface TransactionAttribute extends TransactionDefinition { - + + String getQualifier(); + /** * Should we roll back on the given exception? * @param ex the exception to evaluate diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java index 279bdbb1fab..725445634b7 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -94,11 +94,12 @@ public class TransactionInterceptor extends TransactionAspectSupport implements // If the transaction attribute is null, the method is non-transactional. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(invocation.getMethod(), targetClass); + final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(invocation.getMethod()); - if (txAttr == null || !(getTransactionManager() instanceof CallbackPreferringPlatformTransactionManager)) { + if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. - TransactionInfo txInfo = createTransactionIfNecessary(txAttr, joinpointIdentification); + TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // This is an around advice: Invoke the next interceptor in the chain. @@ -120,10 +121,10 @@ public class TransactionInterceptor extends TransactionAspectSupport implements else { // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. try { - Object result = ((CallbackPreferringPlatformTransactionManager) getTransactionManager()).execute(txAttr, - new TransactionCallback() { + Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, + new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { - TransactionInfo txInfo = prepareTransactionInfo(txAttr, joinpointIdentification, status); + TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); try { return invocation.proceed(); } @@ -167,6 +168,15 @@ public class TransactionInterceptor extends TransactionAspectSupport implements // Serialization support //--------------------------------------------------------------------- + private void writeObject(ObjectOutputStream oos) throws IOException { + // Rely on default serialization, although this class itself doesn't carry state anyway... + oos.defaultWriteObject(); + + // Deserialize superclass fields. + oos.writeObject(getTransactionManager()); + oos.writeObject(getTransactionAttributeSource()); + } + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Rely on default serialization, although this class itself doesn't carry state anyway... ois.defaultReadObject(); @@ -178,15 +188,6 @@ public class TransactionInterceptor extends TransactionAspectSupport implements setTransactionAttributeSource((TransactionAttributeSource) ois.readObject()); } - private void writeObject(ObjectOutputStream oos) throws IOException { - // Rely on default serialization, although this class itself doesn't carry state anyway... - oos.defaultWriteObject(); - - // Deserialize superclass fields. - oos.writeObject(getTransactionManager()); - oos.writeObject(getTransactionAttributeSource()); - } - /** * Internal holder class for a Throwable, used as a return value diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java index cea42425811..762b5122694 100644 --- a/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java +++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java @@ -23,7 +23,6 @@ import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.transaction.PlatformTransactionManager; @@ -171,12 +170,7 @@ public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBe * @see org.springframework.transaction.PlatformTransactionManager */ public void setBeanFactory(BeanFactory beanFactory) { - if (this.transactionInterceptor.getTransactionManager() == null && - beanFactory instanceof ListableBeanFactory) { - ListableBeanFactory lbf = (ListableBeanFactory) beanFactory; - PlatformTransactionManager ptm = BeanFactoryUtils.beanOfTypeIncludingAncestors(lbf, PlatformTransactionManager.class); - this.transactionInterceptor.setTransactionManager(ptm); - } + this.transactionInterceptor.setBeanFactory(beanFactory); } diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java index 072393d935a..8ea43bb1bfe 100644 --- a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/AnnotationDrivenTests.java @@ -22,6 +22,7 @@ import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.AopUtils; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.transaction.CallCountingTransactionManager; import org.springframework.transaction.support.TransactionSynchronizationManager; /** @@ -32,16 +33,34 @@ public class AnnotationDrivenTests extends TestCase { public void testWithProxyTargetClass() throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("annotationDrivenProxyTargetClassTests.xml", getClass()); - TransactionalService service = (TransactionalService) context.getBean("service"); + CallCountingTransactionManager tm1 = context.getBean("transactionManager1", CallCountingTransactionManager.class); + CallCountingTransactionManager tm2 = context.getBean("transactionManager2", CallCountingTransactionManager.class); + TransactionalService service = context.getBean("service", TransactionalService.class); assertTrue(AopUtils.isCglibProxy(service)); - service.setBeanName("someName"); + service.setSomething("someName"); + assertEquals(1, tm1.commits); + assertEquals(0, tm2.commits); + service.doSomething(); + assertEquals(1, tm1.commits); + assertEquals(1, tm2.commits); + service.setSomething("someName"); + assertEquals(2, tm1.commits); + assertEquals(1, tm2.commits); + service.doSomething(); + assertEquals(2, tm1.commits); + assertEquals(2, tm2.commits); } public static class TransactionCheckingInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation methodInvocation) throws Throwable { - assertTrue(TransactionSynchronizationManager.isActualTransactionActive()); + if (methodInvocation.getMethod().getName().equals("setSomething")) { + assertTrue(TransactionSynchronizationManager.isSynchronizationActive()); + } + else { + assertFalse(TransactionSynchronizationManager.isSynchronizationActive()); + } return methodInvocation.proceed(); } } diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionalService.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionalService.java index 74aee4b1149..1c55e1c2220 100644 --- a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionalService.java +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/TransactionalService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2009 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,16 @@ import org.springframework.transaction.annotation.Transactional; /** * @author Rob Harrop + * @author Juergen Hoeller */ -@Transactional -public class TransactionalService implements BeanNameAware { +public class TransactionalService { - public void setBeanName(String name) { - // just for testing :) + @Transactional("synch") + public void setSomething(String name) { + } + + @Transactional("noSynch") + public void doSomething() { } } diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenProxyTargetClassTests.xml b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenProxyTargetClassTests.xml index 8f7ddc23b2c..ea4f7189c83 100644 --- a/org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenProxyTargetClassTests.xml +++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/config/annotationDrivenProxyTargetClassTests.xml @@ -2,7 +2,7 @@ + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> @@ -12,8 +12,15 @@ - + + + + + + + +