diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java index a85063a6d4..b9ed551b74 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -30,6 +30,8 @@ import org.springframework.transaction.interceptor.NoRollbackRuleAttribute; import org.springframework.transaction.interceptor.RollbackRuleAttribute; import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttribute; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Strategy implementation for parsing Spring's {@link Transactional} annotation. @@ -70,7 +72,13 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP rbta.setPropagationBehavior(propagation.value()); Isolation isolation = attributes.getEnum("isolation"); rbta.setIsolationLevel(isolation.value()); + rbta.setTimeout(attributes.getNumber("timeout").intValue()); + String timeoutString = attributes.getString("timeoutString"); + Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0, + "Specify 'timeout' or 'timeoutString', not both"); + rbta.setTimeoutString(timeoutString); + rbta.setReadOnly(attributes.getBoolean("readOnly")); rbta.setQualifier(attributes.getString("value")); rbta.setLabels(Arrays.asList(attributes.getStringArray("label"))); diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java index e4eda9d117..4af6be7675 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java @@ -84,6 +84,18 @@ public @interface Transactional { @AliasFor("value") String transactionManager() default ""; + /** + * Defines zero (0) or more transaction labels. Labels may be used to + * describe a transaction and they can be evaluated by individual transaction + * manager. Labels may serve a solely descriptive purpose or map to + * pre-defined transaction manager-specific options. + *

See the description of the actual transaction manager implementation + * how it evaluates transaction labels. + * @since 5.3 + * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#getLabels() + */ + String[] label() default {}; + /** * The transaction propagation type. *

Defaults to {@link Propagation#REQUIRED}. @@ -111,10 +123,23 @@ public @interface Transactional { *

Exclusively designed for use with {@link Propagation#REQUIRED} or * {@link Propagation#REQUIRES_NEW} since it only applies to newly started * transactions. + * @return the timeout in seconds * @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout() */ int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; + /** + * The timeout for this transaction (in seconds). + *

Defaults to the default timeout of the underlying transaction system. + *

Exclusively designed for use with {@link Propagation#REQUIRED} or + * {@link Propagation#REQUIRES_NEW} since it only applies to newly started + * transactions. + * @return the timeout in seconds as a String value, e.g. a placeholder + * @since 5.3 + * @see org.springframework.transaction.interceptor.TransactionAttribute#getTimeout() + */ + String timeoutString() default ""; + /** * A boolean flag that can be set to {@code true} if the transaction is * effectively read-only, allowing for corresponding optimizations at runtime. @@ -190,17 +215,4 @@ public @interface Transactional { */ String[] noRollbackForClassName() default {}; - /** - * Defines zero (0) or more transaction labels. Labels may be used to - * describe a transaction and they can be evaluated by individual transaction - * manager. Labels may serve a solely descriptive purpose or map to - * pre-defined transaction manager-specific options. - *

See the description of the actual transaction manager implementation - * how it evaluates transaction labels. - * - * @since 5.3 - * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#getLabels() - */ - String[] label() default {}; - } diff --git a/spring-tx/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java b/spring-tx/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java index 41a1e61370..171b626109 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2020 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. @@ -116,12 +116,7 @@ class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation); } if (StringUtils.hasText(timeout)) { - try { - attribute.setTimeout(Integer.parseInt(timeout)); - } - catch (NumberFormatException ex) { - parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle); - } + attribute.setTimeoutString(timeout); } if (StringUtils.hasText(readOnly)) { attribute.setReadOnly(Boolean.parseBoolean(methodEle.getAttribute(READ_ONLY_ATTRIBUTE))); diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java index 9ea4456bac..80983806ed 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -25,9 +25,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.support.AopUtils; +import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.core.MethodClassKey; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; +import org.springframework.util.StringValueResolver; /** * Abstract implementation of {@link TransactionAttributeSource} that caches @@ -49,7 +51,8 @@ import org.springframework.util.ClassUtils; * @author Juergen Hoeller * @since 1.1 */ -public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource { +public abstract class AbstractFallbackTransactionAttributeSource + implements TransactionAttributeSource, EmbeddedValueResolverAware { /** * Canonical value held in cache to indicate no transaction attribute was @@ -71,6 +74,9 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran */ protected final Log logger = LogFactory.getLog(getClass()); + @Nullable + private transient StringValueResolver embeddedValueResolver; + /** * Cache of TransactionAttributes, keyed by method on a specific target class. *

As this base class is not marked Serializable, the cache will be recreated @@ -79,6 +85,12 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran private final Map attributeCache = new ConcurrentHashMap<>(1024); + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.embeddedValueResolver = resolver; + } + + /** * Determine the transaction attribute for this method invocation. *

Defaults to the class's transaction attribute if no method attribute is found. @@ -117,7 +129,9 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran else { String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); if (txAttr instanceof DefaultTransactionAttribute) { - ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification); + DefaultTransactionAttribute dta = (DefaultTransactionAttribute) txAttr; + dta.setDescriptor(methodIdentification); + dta.resolveAttributeStrings(this.embeddedValueResolver); } if (logger.isTraceEnabled()) { logger.trace("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr); diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java index ec40a95c65..cdf73d0034 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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,10 +18,13 @@ package org.springframework.transaction.interceptor; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; import org.springframework.lang.Nullable; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; /** * Spring's common transaction attribute implementation. @@ -36,10 +39,13 @@ import org.springframework.util.StringUtils; public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute { @Nullable - private String qualifier; + private String descriptor; @Nullable - private String descriptor; + private String timeoutString; + + @Nullable + private String qualifier; private Collection labels = Collections.emptyList(); @@ -83,11 +89,54 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im } + /** + * Set a descriptor for this transaction attribute, + * e.g. indicating where the attribute is applying. + * @since 4.3.4 + */ + public void setDescriptor(@Nullable String descriptor) { + this.descriptor = descriptor; + } + + /** + * Return a descriptor for this transaction attribute, + * or {@code null} if none. + * @since 4.3.4 + */ + @Nullable + public String getDescriptor() { + return this.descriptor; + } + + /** + * Set the timeout to apply, if any, + * as a String value that resolves to a number of seconds. + * @since 5.3 + * @see #setTimeout + * @see #resolveAttributeStrings + */ + public void setTimeoutString(@Nullable String timeoutString) { + this.timeoutString = timeoutString; + } + + /** + * Return the timeout to apply, if any, + * as a String value that resolves to a number of seconds. + * @since 5.3 + * @see #getTimeout + * @see #resolveAttributeStrings + */ + @Nullable + public String getTimeoutString() { + return this.timeoutString; + } + /** * Associate a qualifier value with this transaction attribute. *

This may be used for choosing a corresponding transaction manager * to process this specific transaction. * @since 3.0 + * @see #resolveAttributeStrings */ public void setQualifier(@Nullable String qualifier) { this.qualifier = qualifier; @@ -108,6 +157,7 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im *

This may be used for applying specific transactional behavior * or follow a purely descriptive nature. * @since 5.3 + * @see #resolveAttributeStrings */ public void setLabels(Collection labels) { this.labels = labels; @@ -118,25 +168,6 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im return this.labels; } - /** - * Set a descriptor for this transaction attribute, - * e.g. indicating where the attribute is applying. - * @since 4.3.4 - */ - public void setDescriptor(@Nullable String descriptor) { - this.descriptor = descriptor; - } - - /** - * Return a descriptor for this transaction attribute, - * or {@code null} if none. - * @since 4.3.4 - */ - @Nullable - public String getDescriptor() { - return this.descriptor; - } - /** * The default behavior is as with EJB: rollback on unchecked exception * ({@link RuntimeException}), assuming an unexpected outcome outside of any @@ -157,6 +188,42 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im } + /** + * Resolve attribute values that are defined as resolvable Strings: + * {@link #setTimeoutString}, {@link #setQualifier}, {@link #setLabels}. + * This is typically used for resolving "${...}" placeholders. + * @param resolver the embedded value resolver to apply, if any + * @since 5.3 + */ + public void resolveAttributeStrings(@Nullable StringValueResolver resolver) { + String timeoutString = this.timeoutString; + if (StringUtils.hasText(timeoutString)) { + if (resolver != null) { + timeoutString = resolver.resolveStringValue(timeoutString); + } + if (StringUtils.hasLength(timeoutString)) { + try { + setTimeout(Integer.parseInt(timeoutString)); + } + catch (RuntimeException ex) { + throw new IllegalArgumentException( + "Invalid timeoutString value \"" + timeoutString + "\" - cannot parse into int"); + } + } + } + + if (resolver != null) { + if (this.qualifier != null) { + this.qualifier = resolver.resolveStringValue(this.qualifier); + } + Set resolvedLabels = new LinkedHashSet<>(this.labels.size()); + for (String label : this.labels) { + resolvedLabels.add(resolver.resolveStringValue(label)); + } + this.labels = resolvedLabels; + } + } + /** * Return an identifying description for this transaction attribute. *

Available to subclasses, for inclusion in their {@code toString()} result. diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java index 38f33c474f..c5698a7cb1 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -31,6 +31,7 @@ import org.springframework.util.ObjectUtils; * methods being handled by a transaction interceptor. * * @author Colin Sampaleanu + * @author Juergen Hoeller * @since 15.10.2003 * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean * @see org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator @@ -48,6 +49,9 @@ public class MatchAlwaysTransactionAttributeSource implements TransactionAttribu * @see org.springframework.transaction.interceptor.TransactionAttributeEditor */ public void setTransactionAttribute(TransactionAttribute transactionAttribute) { + if (transactionAttribute instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) transactionAttribute).resolveAttributeStrings(null); + } this.transactionAttribute = transactionAttribute; } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java index b67eb000e3..9407b1881a 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -27,11 +27,13 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; +import org.springframework.util.StringValueResolver; /** * Simple {@link TransactionAttributeSource} implementation that @@ -44,7 +46,7 @@ import org.springframework.util.PatternMatchUtils; * @see NameMatchTransactionAttributeSource */ public class MethodMapTransactionAttributeSource - implements TransactionAttributeSource, BeanClassLoaderAware, InitializingBean { + implements TransactionAttributeSource, EmbeddedValueResolverAware, BeanClassLoaderAware, InitializingBean { /** Logger available to subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); @@ -53,6 +55,9 @@ public class MethodMapTransactionAttributeSource @Nullable private Map methodMap; + @Nullable + private StringValueResolver embeddedValueResolver; + @Nullable private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); @@ -83,6 +88,11 @@ public class MethodMapTransactionAttributeSource this.methodMap = methodMap; } + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.embeddedValueResolver = resolver; + } + @Override public void setBeanClassLoader(ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; @@ -189,6 +199,9 @@ public class MethodMapTransactionAttributeSource if (logger.isDebugEnabled()) { logger.debug("Adding transactional method [" + method + "] with attribute [" + attr + "]"); } + if (this.embeddedValueResolver != null && attr instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) attr).resolveAttributeStrings(this.embeddedValueResolver); + } this.transactionAttributeMap.put(method, attr); } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java index 20c0c4ac39..f09c5501ce 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -26,10 +26,13 @@ import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.PatternMatchUtils; +import org.springframework.util.StringValueResolver; /** * Simple {@link TransactionAttributeSource} implementation that @@ -41,7 +44,8 @@ import org.springframework.util.PatternMatchUtils; * @see MethodMapTransactionAttributeSource */ @SuppressWarnings("serial") -public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable { +public class NameMatchTransactionAttributeSource + implements TransactionAttributeSource, EmbeddedValueResolverAware, InitializingBean, Serializable { /** * Logger available to subclasses. @@ -50,7 +54,10 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute protected static final Log logger = LogFactory.getLog(NameMatchTransactionAttributeSource.class); /** Keys are method names; values are TransactionAttributes. */ - private Map nameMap = new HashMap<>(); + private final Map nameMap = new HashMap<>(); + + @Nullable + private StringValueResolver embeddedValueResolver; /** @@ -94,9 +101,26 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute if (logger.isDebugEnabled()) { logger.debug("Adding transactional method [" + methodName + "] with attribute [" + attr + "]"); } + if (this.embeddedValueResolver != null && attr instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) attr).resolveAttributeStrings(this.embeddedValueResolver); + } this.nameMap.put(methodName, attr); } + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.embeddedValueResolver = resolver; + } + + @Override + public void afterPropertiesSet() { + for (TransactionAttribute attr : this.nameMap.values()) { + if (attr instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) attr).resolveAttributeStrings(this.embeddedValueResolver); + } + } + } + @Override @Nullable diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java index f89a83bd74..e6a13c8130 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 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. @@ -69,7 +69,7 @@ public class TransactionAttributeEditor extends PropertyEditorSupport { } else if (trimmedToken.startsWith(RuleBasedTransactionAttribute.PREFIX_TIMEOUT)) { String value = trimmedToken.substring(DefaultTransactionAttribute.PREFIX_TIMEOUT.length()); - attr.setTimeout(Integer.parseInt(value)); + attr.setTimeoutString(value); } else if (trimmedToken.equals(RuleBasedTransactionAttribute.READ_ONLY_MARKER)) { attr.setReadOnly(true); @@ -84,6 +84,7 @@ public class TransactionAttributeEditor extends PropertyEditorSupport { throw new IllegalArgumentException("Invalid transaction attribute token: [" + trimmedToken + "]"); } } + attr.resolveAttributeStrings(null); // placeholders expected to be pre-resolved setValue(attr); } else { diff --git a/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx.xsd b/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx.xsd index 2d71c55383..f6b3f2fbdc 100644 --- a/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx.xsd +++ b/spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx.xsd @@ -218,7 +218,7 @@ - + ("${myTimeout}".equals(strVal) ? "5" : strVal)); + TransactionAttribute actual = atas.getTransactionAttribute(interfaceMethod, TestBean3.class); assertThat(actual.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_REQUIRES_NEW); assertThat(actual.getIsolationLevel()).isEqualTo(TransactionAttribute.ISOLATION_REPEATABLE_READ); assertThat(actual.getTimeout()).isEqualTo(5); assertThat(actual.isReadOnly()).isTrue(); + TransactionAttribute actual2 = atas.getTransactionAttribute(interfaceMethod2, TestBean3.class); + assertThat(actual2.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_REQUIRES_NEW); + assertThat(actual2.getIsolationLevel()).isEqualTo(TransactionAttribute.ISOLATION_REPEATABLE_READ); + assertThat(actual2.getTimeout()).isEqualTo(5); + assertThat(actual2.isReadOnly()).isTrue(); + RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); rbta.getRollbackRules().add(new RollbackRuleAttribute(Exception.class)); rbta.getRollbackRules().add(new NoRollbackRuleAttribute(IOException.class)); assertThat(((RuleBasedTransactionAttribute) actual).getRollbackRules()).isEqualTo(rbta.getRollbackRules()); - TransactionAttribute actual2 = atas.getTransactionAttribute(interfaceMethod2, TestBean3.class); - assertThat(actual2.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_REQUIRED); + TransactionAttribute actual3 = atas.getTransactionAttribute(interfaceMethod3, TestBean3.class); + assertThat(actual3.getPropagationBehavior()).isEqualTo(TransactionAttribute.PROPAGATION_REQUIRED); } @Test @@ -585,6 +594,9 @@ public class AnnotationTransactionAttributeSourceTests { } @Override + @Transactional(propagation = Propagation.REQUIRES_NEW, isolation=Isolation.REPEATABLE_READ, + timeoutString = "${myTimeout}", readOnly = true, rollbackFor = Exception.class, + noRollbackFor = IOException.class) public void setAge(int age) { this.age = age; } diff --git a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java index d7ba674273..a9f74b8852 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java @@ -18,6 +18,7 @@ package org.springframework.transaction.annotation; import java.util.Collection; import java.util.Map; +import java.util.Properties; import org.junit.jupiter.api.Test; @@ -31,13 +32,16 @@ import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionManager; import org.springframework.transaction.config.TransactionManagementConfigUtils; import org.springframework.transaction.event.TransactionalEventListenerFactory; +import org.springframework.transaction.interceptor.TransactionAttribute; import org.springframework.transaction.testfixture.CallCountingTransactionManager; import static org.assertj.core.api.Assertions.assertThat; @@ -99,6 +103,9 @@ public class EnableTransactionManagementTests { assertThat(txManager.begun).isEqualTo(1); assertThat(txManager.commits).isEqualTo(1); assertThat(txManager.rollbacks).isEqualTo(0); + assertThat(txManager.lastDefinition.isReadOnly()).isTrue(); + assertThat(txManager.lastDefinition.getTimeout()).isEqualTo(5); + assertThat(((TransactionAttribute) txManager.lastDefinition).getLabels()).contains("LABEL"); ctx.close(); } @@ -266,7 +273,7 @@ public class EnableTransactionManagementTests { @Service public static class TransactionalTestBean { - @Transactional(readOnly = true) + @Transactional(label = "${myLabel}", timeoutString = "${myTimeout}", readOnly = true) public Collection findAllFoos() { return null; } @@ -275,14 +282,31 @@ public class EnableTransactionManagementTests { public void saveQualifiedFoo() { } - @Transactional(transactionManager = "qualifiedTransactionManager") + @Transactional(transactionManager = "${myTransactionManager}") public void saveQualifiedFooWithAttributeAlias() { } } + @Configuration + static class PlaceholderConfig { + + @Bean + public PropertySourcesPlaceholderConfigurer placeholderConfigurer() { + PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer(); + Properties props = new Properties(); + props.setProperty("myLabel", "LABEL"); + props.setProperty("myTimeout", "5"); + props.setProperty("myTransactionManager", "qualifiedTransactionManager"); + pspc.setProperties(props); + return pspc; + } + } + + @Configuration @EnableTransactionManagement + @Import(PlaceholderConfig.class) static class EnableTxConfig { } @@ -294,6 +318,7 @@ public class EnableTransactionManagementTests { @Configuration @EnableTransactionManagement + @Import(PlaceholderConfig.class) @Conditional(NeverCondition.class) static class ParentEnableTxConfig { @@ -433,6 +458,7 @@ public class EnableTransactionManagementTests { @Configuration @EnableTransactionManagement + @Import(PlaceholderConfig.class) static class Spr11915Config { @Autowired diff --git a/spring-tx/src/test/resources/org/springframework/transaction/txNamespaceHandlerTests.xml b/spring-tx/src/test/resources/org/springframework/transaction/txNamespaceHandlerTests.xml index 5214570c81..e048da5671 100644 --- a/spring-tx/src/test/resources/org/springframework/transaction/txNamespaceHandlerTests.xml +++ b/spring-tx/src/test/resources/org/springframework/transaction/txNamespaceHandlerTests.xml @@ -7,13 +7,17 @@ http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> + + + + - +