Extend transaction attributes with labels

TransactionAttribute now exposes a labels attribute that associates a
descriptive array of labels with a transaction.

Labels may be of a pure descriptive nature or may get evaluated by
transaction managers to associate technology-specific behavior
with the actual transaction.
This commit is contained in:
Mark Paluch
2020-04-09 14:55:18 +02:00
committed by Juergen Hoeller
parent 3e736898a6
commit 2aa8aef216
7 changed files with 102 additions and 6 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.transaction.annotation;
import java.io.Serializable;
import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -34,6 +35,7 @@ import org.springframework.transaction.interceptor.TransactionAttribute;
* Strategy implementation for parsing Spring's {@link Transactional} annotation.
*
* @author Juergen Hoeller
* @author Mark Paluch
* @since 2.5
*/
@SuppressWarnings("serial")
@@ -71,6 +73,7 @@ public class SpringTransactionAnnotationParser implements TransactionAnnotationP
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
rbta.setLabels(Arrays.asList(attributes.getStringArray("label")));
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {

View File

@@ -51,6 +51,7 @@ import org.springframework.transaction.TransactionDefinition;
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @author Sam Brannen
* @author Mark Paluch
* @since 1.2
* @see org.springframework.transaction.interceptor.TransactionAttribute
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute
@@ -187,4 +188,17 @@ 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 {};
}

View File

@@ -16,6 +16,9 @@
package org.springframework.transaction.interceptor;
import java.util.Collection;
import java.util.Collections;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.StringUtils;
@@ -26,6 +29,7 @@ import org.springframework.util.StringUtils;
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Mark Paluch
* @since 16.03.2003
*/
@SuppressWarnings("serial")
@@ -37,6 +41,8 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im
@Nullable
private String descriptor;
private Collection<String> labels = Collections.emptyList();
/**
* Create a new DefaultTransactionAttribute, with default settings.
@@ -97,6 +103,21 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im
return this.qualifier;
}
/**
* Associate one or more labels with this transaction attribute.
* <p>This may be used for applying specific transactional behavior
* or follow a purely descriptive nature.
* @since 5.3
*/
public void setLabels(Collection<String> labels) {
this.labels = labels;
}
@Override
public Collection<String> getLabels() {
return this.labels;
}
/**
* Set a descriptor for this transaction attribute,
* e.g. indicating where the attribute is applying.
@@ -145,6 +166,9 @@ public class DefaultTransactionAttribute extends DefaultTransactionDefinition im
if (StringUtils.hasText(this.qualifier)) {
result.append("; '").append(this.qualifier).append("'");
}
if (!this.labels.isEmpty()) {
result.append("; ").append(this.labels);
}
return result;
}

View File

@@ -17,6 +17,7 @@
package org.springframework.transaction.interceptor;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.lang.Nullable;
import org.springframework.transaction.support.DelegatingTransactionDefinition;
@@ -28,6 +29,7 @@ import org.springframework.transaction.support.DelegatingTransactionDefinition;
* to the target instance.
*
* @author Juergen Hoeller
* @author Mark Paluch
* @since 1.2
*/
@SuppressWarnings("serial")
@@ -53,6 +55,11 @@ public abstract class DelegatingTransactionAttribute extends DelegatingTransacti
return this.targetAttribute.getQualifier();
}
@Override
public Collection<String> getLabels() {
return this.targetAttribute.getLabels();
}
@Override
public boolean rollbackOn(Throwable ex) {
return this.targetAttribute.rollbackOn(ex);

View File

@@ -16,6 +16,8 @@
package org.springframework.transaction.interceptor;
import java.util.Collection;
import org.springframework.lang.Nullable;
import org.springframework.transaction.TransactionDefinition;
@@ -26,6 +28,7 @@ import org.springframework.transaction.TransactionDefinition;
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Mark Paluch
* @since 16.03.2003
* @see DefaultTransactionAttribute
* @see RuleBasedTransactionAttribute
@@ -41,6 +44,14 @@ public interface TransactionAttribute extends TransactionDefinition {
@Nullable
String getQualifier();
/**
* Return labels associated with this transaction attribute.
* <p>This may be used for applying specific transactional behavior
* or follow a purely descriptive nature.
* @since 5.3
*/
Collection<String> getLabels();
/**
* Should we roll back on the given exception?
* @param ex the exception to evaluate

View File

@@ -45,6 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Colin Sampaleanu
* @author Juergen Hoeller
* @author Sam Brannen
* @author Mark Paluch
*/
public class AnnotationTransactionAttributeSourceTests {
@@ -183,6 +184,21 @@ public class AnnotationTransactionAttributeSourceTests {
assertThat(actual.rollbackOn(new IOException())).isFalse();
}
@Test
public void labelsAreApplied() throws Exception {
Method method = TestBean11.class.getMethod("getAge");
AnnotationTransactionAttributeSource atas = new AnnotationTransactionAttributeSource();
TransactionAttribute actual = atas.getTransactionAttribute(method, TestBean11.class);
assertThat(actual.getLabels()).containsOnly("retryable", "long-running");
method = TestBean11.class.getMethod("setAge", Integer.TYPE);
actual = atas.getTransactionAttribute(method, method.getDeclaringClass());
assertThat(actual.getLabels()).containsOnly("short-running");
}
/**
* Test that transaction attribute is inherited from class
* if not specified on method.
@@ -693,6 +709,21 @@ public class AnnotationTransactionAttributeSourceTests {
}
}
@Transactional(label = {"retryable", "long-running"})
static class TestBean11 {
private int age = 10;
@Transactional(label = "short-running")
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
static class Ejb3AnnotatedBean1 implements ITestBean1 {

View File

@@ -1423,6 +1423,11 @@ properties of the `@Transactional` annotation:
| `noRollbackForClassName`
| Array of `String` class names, which must be derived from `Throwable.`
| Optional array of names of exception classes that must not cause rollback.
| `label`
| Array of `String` labels to add an expressive description to the transaction.
| Labels may be evaluated by transaction managers to associate
implementation-specific behavior with the actual transaction.
|===
Currently, you cannot have explicit control over the name of a transaction, where 'name'
@@ -1508,13 +1513,13 @@ following annotation definitions:
----
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
@Transactional(value = "order", label = "causal-consistency")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
@Transactional("account", label = "retryable")
public @interface AccountTx {
}
----
@@ -1523,12 +1528,12 @@ following annotation definitions:
----
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional("order")
@Transactional(value = "order", label = ["causal-consistency"])
annotation class OrderTx
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional("account")
@Transactional(value = "account", label = ["retryable"])
annotation class AccountTx
----
@@ -1567,8 +1572,9 @@ The preceding annotations lets us write the example from the previous section as
}
----
In the preceding example, we used the syntax to define the transaction manager qualifier, but we could also
have included propagation behavior, rollback rules, timeouts, and other features.
In the preceding example, we used the syntax to define the transaction manager qualifier
and transactional labels, but we could also have included propagation behavior,
rollback rules, timeouts, and other features.
[[tx-propagation]]