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