Browse Source

Introduce resolvable timeout attribute on @Transactional and <tx:method>

Placeholders get resolved in timeoutString, qualifier and labels now.

Closes gh-25052
pull/25075/head
Juergen Hoeller 5 years ago
parent
commit
dd0d0d51f6
  1. 10
      spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java
  2. 38
      spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java
  3. 9
      spring-tx/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java
  4. 20
      spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java
  5. 111
      spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java
  6. 6
      spring-tx/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java
  7. 17
      spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java
  8. 30
      spring-tx/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java
  9. 5
      spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java
  10. 2
      spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx.xsd
  11. 3
      spring-tx/src/test/java/org/springframework/transaction/TxNamespaceHandlerTests.java
  12. 20
      spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java
  13. 30
      spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java
  14. 6
      spring-tx/src/test/resources/org/springframework/transaction/txNamespaceHandlerTests.xml

10
spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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")));

38
spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java

@ -84,6 +84,18 @@ public @interface Transactional { @@ -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.
* <p>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.
* <p>Defaults to {@link Propagation#REQUIRED}.
@ -111,10 +123,23 @@ public @interface Transactional { @@ -111,10 +123,23 @@ public @interface Transactional {
* <p>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).
* <p>Defaults to the default timeout of the underlying transaction system.
* <p>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 { @@ -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.
* <p>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 {};
}

9
spring-tx/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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)));

20
spring-tx/src/main/java/org/springframework/transaction/interceptor/AbstractFallbackTransactionAttributeSource.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -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.
* <p>As this base class is not marked Serializable, the cache will be recreated
@ -79,6 +85,12 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran @@ -79,6 +85,12 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran
private final Map<Object, TransactionAttribute> attributeCache = new ConcurrentHashMap<>(1024);
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.embeddedValueResolver = resolver;
}
/**
* Determine the transaction attribute for this method invocation.
* <p>Defaults to the class's transaction attribute if no method attribute is found.
@ -117,7 +129,9 @@ public abstract class AbstractFallbackTransactionAttributeSource implements Tran @@ -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);

111
spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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<String> labels = Collections.emptyList();
@ -83,11 +89,54 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im @@ -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.
* <p>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 @@ -108,6 +157,7 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im
* <p>This may be used for applying specific transactional behavior
* or follow a purely descriptive nature.
* @since 5.3
* @see #resolveAttributeStrings
*/
public void setLabels(Collection<String> labels) {
this.labels = labels;
@ -118,25 +168,6 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im @@ -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 @@ -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<String> 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.
* <p>Available to subclasses, for inclusion in their {@code toString()} result.

6
spring-tx/src/main/java/org/springframework/transaction/interceptor/MatchAlwaysTransactionAttributeSource.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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;
}

17
spring-tx/src/main/java/org/springframework/transaction/interceptor/MethodMapTransactionAttributeSource.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -53,6 +55,9 @@ public class MethodMapTransactionAttributeSource
@Nullable
private Map<String, TransactionAttribute> methodMap;
@Nullable
private StringValueResolver embeddedValueResolver;
@Nullable
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
@ -83,6 +88,11 @@ public class MethodMapTransactionAttributeSource @@ -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 @@ -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);
}

30
spring-tx/src/main/java/org/springframework/transaction/interceptor/NameMatchTransactionAttributeSource.java

@ -1,5 +1,5 @@ @@ -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; @@ -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; @@ -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 @@ -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<String, TransactionAttribute> nameMap = new HashMap<>();
private final Map<String, TransactionAttribute> nameMap = new HashMap<>();
@Nullable
private StringValueResolver embeddedValueResolver;
/**
@ -94,9 +101,26 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute @@ -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

5
spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeEditor.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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 {

2
spring-tx/src/main/resources/org/springframework/transaction/config/spring-tx.xsd

@ -218,7 +218,7 @@ @@ -218,7 +218,7 @@
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="timeout" type="xsd:int" default="-1">
<xsd:attribute name="timeout" type="xsd:string" default="">
<xsd:annotation>
<xsd:documentation><![CDATA[
The transaction timeout value (in seconds).

3
spring-tx/src/test/java/org/springframework/transaction/TxNamespaceHandlerTests.java

@ -1,5 +1,5 @@ @@ -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,6 +69,7 @@ public class TxNamespaceHandlerTests { @@ -69,6 +69,7 @@ public class TxNamespaceHandlerTests {
assertThat(ptm.begun).as("Should not have any started transactions").isEqualTo(0);
testBean.getName();
assertThat(ptm.lastDefinition.isReadOnly()).isTrue();
assertThat(ptm.lastDefinition.getTimeout()).isEqualTo(5);
assertThat(ptm.begun).as("Should have 1 started transaction").isEqualTo(1);
assertThat(ptm.commits).as("Should have 1 committed transaction").isEqualTo(1);

20
spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java

@ -1,5 +1,5 @@ @@ -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.
@ -140,22 +140,31 @@ public class AnnotationTransactionAttributeSourceTests { @@ -140,22 +140,31 @@ public class AnnotationTransactionAttributeSourceTests {
@Test
public void transactionAttributeOnTargetClassMethodOverridesAttributeOnInterfaceMethod() throws Exception {
Method interfaceMethod = ITestBean3.class.getMethod("getAge");
Method interfaceMethod2 = ITestBean3.class.getMethod("getName");
Method interfaceMethod2 = ITestBean3.class.getMethod("setAge", int.class);
Method interfaceMethod3 = ITestBean3.class.getMethod("getName");
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
atas.setEmbeddedValueResolver(strVal -> ("${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 { @@ -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;
}

30
spring-tx/src/test/java/org/springframework/transaction/annotation/EnableTransactionManagementTests.java

@ -18,6 +18,7 @@ package org.springframework.transaction.annotation; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -433,6 +458,7 @@ public class EnableTransactionManagementTests {
@Configuration
@EnableTransactionManagement
@Import(PlaceholderConfig.class)
static class Spr11915Config {
@Autowired

6
spring-tx/src/test/resources/org/springframework/transaction/txNamespaceHandlerTests.xml

@ -7,13 +7,17 @@ @@ -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">
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="properties" value="myTimeout=5"/>
</bean>
<aop:config>
<aop:advisor pointcut="execution(* *..ITestBean.*(..))" advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="get*" timeout="5" read-only="true"/>
<tx:method name="set*"/>
<tx:method name="exceptional"/>
</tx:attributes>

Loading…
Cancel
Save