Introduce resolvable timeout attribute on @Transactional and <tx:method>
Placeholders get resolved in timeoutString, qualifier and labels now. Closes gh-25052
This commit is contained in:
		
							parent
							
								
									273d952ddf
								
							
						
					
					
						commit
						dd0d0d51f6
					
				| 
						 | 
				
			
			@ -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")));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		|||
	 * <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 {
 | 
			
		|||
	 */
 | 
			
		||||
	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 {};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)));
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
	 * <p>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<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
 | 
			
		|||
			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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String> 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.
 | 
			
		||||
	 * <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
 | 
			
		|||
	 * <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
 | 
			
		|||
		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<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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String, TransactionAttribute> 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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<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
 | 
			
		|||
		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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		|||
		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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		|||
	@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 {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		@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;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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…
	
		Reference in New Issue