From 2aa8aef216934e65a78610707dcfe632a94b0ba5 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 9 Apr 2020 14:55:18 +0200 Subject: [PATCH] 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. --- .../SpringTransactionAnnotationParser.java | 3 ++ .../transaction/annotation/Transactional.java | 14 +++++++++ .../DefaultTransactionAttribute.java | 24 ++++++++++++++ .../DelegatingTransactionAttribute.java | 7 +++++ .../interceptor/TransactionAttribute.java | 11 +++++++ ...tationTransactionAttributeSourceTests.java | 31 +++++++++++++++++++ src/docs/asciidoc/data-access.adoc | 18 +++++++---- 7 files changed, 102 insertions(+), 6 deletions(-) diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java index 82a0905e88..a85063a6d4 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/SpringTransactionAnnotationParser.java @@ -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 rollbackRules = new ArrayList<>(); for (Class rbRule : attributes.getClassArray("rollbackFor")) { diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java index 3c2046c97a..fbb88a52f2 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java @@ -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. + *

See the description of the actual transaction manager implementation + * how it evaluates transaction labels. + * + * @since 5.3 + * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#getLabels() + */ + String[] label() default {}; + } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java index 6efeabd08f..ec40a95c65 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DefaultTransactionAttribute.java @@ -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 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. + *

This may be used for applying specific transactional behavior + * or follow a purely descriptive nature. + * @since 5.3 + */ + public void setLabels(Collection labels) { + this.labels = labels; + } + + @Override + public Collection 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; } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java index 45870d0110..7d74ac5897 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/DelegatingTransactionAttribute.java @@ -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 getLabels() { + return this.targetAttribute.getLabels(); + } + @Override public boolean rollbackOn(Throwable ex) { return this.targetAttribute.rollbackOn(ex); diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java index 25ad765848..afddd2f95f 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttribute.java @@ -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. + *

This may be used for applying specific transactional behavior + * or follow a purely descriptive nature. + * @since 5.3 + */ + Collection getLabels(); + /** * Should we roll back on the given exception? * @param ex the exception to evaluate diff --git a/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java b/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java index cf1de5525b..f9d08837b0 100644 --- a/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java +++ b/spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionAttributeSourceTests.java @@ -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 { diff --git a/src/docs/asciidoc/data-access.adoc b/src/docs/asciidoc/data-access.adoc index beef7b7235..2b6ae276f3 100644 --- a/src/docs/asciidoc/data-access.adoc +++ b/src/docs/asciidoc/data-access.adoc @@ -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]]