From 70e6606f0f2aa5aed64a1ed64b262e5e517b3748 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 30 May 2021 17:10:01 +0200 Subject: [PATCH] Improve @Transactional docs regarding method visibility Closes gh-27003 --- .../transaction/annotation/Transactional.java | 19 ++-- src/docs/asciidoc/data-access.adoc | 100 +++++++++++++----- 2 files changed, 82 insertions(+), 37 deletions(-) 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 75aea982850..f539fedef58 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -29,10 +29,13 @@ import org.springframework.transaction.TransactionDefinition; /** * Describes a transaction attribute on an individual method or on a class. * - *

At the class level, this annotation applies as a default to all methods of - * the declaring class and its subclasses. Note that it does not apply to ancestor - * classes up the class hierarchy; methods need to be locally redeclared in order - * to participate in a subclass-level annotation. + *

When this annotation is declared at the class level, it applies as a default + * to all methods of the declaring class and its subclasses. Note that it does not + * apply to ancestor classes up the class hierarchy; inherited methods need to be + * locally redeclared in order to participate in a subclass-level annotation. For + * details on method visibility constraints, consult the + * Transaction Management + * section of the reference manual. * *

This annotation type is generally directly comparable to Spring's * {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute} @@ -46,14 +49,14 @@ import org.springframework.transaction.TransactionDefinition; * consult the {@link org.springframework.transaction.TransactionDefinition} and * {@link org.springframework.transaction.interceptor.TransactionAttribute} javadocs. * - *

This annotation commonly works with thread-bound transactions managed by + *

This annotation commonly works with thread-bound transactions managed by a * {@link org.springframework.transaction.PlatformTransactionManager}, exposing a * transaction to all data access operations within the current execution thread. * Note: This does NOT propagate to newly started threads within the method. * *

Alternatively, this annotation may demarcate a reactive transaction managed - * by {@link org.springframework.transaction.ReactiveTransactionManager} which - * uses the Reactor context instead of thread-local attributes. As a consequence, + * by a {@link org.springframework.transaction.ReactiveTransactionManager} which + * uses the Reactor context instead of thread-local variables. As a consequence, * all participating data access operations need to execute within the same * Reactor context in the same reactive pipeline. * diff --git a/src/docs/asciidoc/data-access.adoc b/src/docs/asciidoc/data-access.adoc index 3c7232490e6..fd38508ddbf 100644 --- a/src/docs/asciidoc/data-access.adoc +++ b/src/docs/asciidoc/data-access.adoc @@ -888,9 +888,9 @@ that test drives the configuration shown earlier: public final class Boot { public static void main(final String[] args) throws Exception { - ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class); - FooService fooService = (FooService) ctx.getBean("fooService"); - fooService.insertFoo (new Foo()); + ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml"); + FooService fooService = ctx.getBean(FooService.class); + fooService.insertFoo(new Foo()); } } ---- @@ -1365,18 +1365,22 @@ Consider the following class definition: @Transactional public class DefaultFooService implements FooService { + @Override public Foo getFoo(String fooName) { // ... } + @Override public Foo getFoo(String fooName, String barName) { // ... } + @Override public void insertFoo(Foo foo) { // ... } + @Override public void updateFoo(Foo foo) { // ... } @@ -1407,11 +1411,13 @@ Consider the following class definition: } ---- -Used at the class level as above, the annotation indicates a default for all public methods -of the declaring class (as well as its subclasses). Alternatively, each method can -get annotated individually. Note that a class-level annotation does not apply to -ancestor classes up the class hierarchy; in such a scenario, methods need to be -locally redeclared in order to participate in a subclass-level annotation. +Used at the class level as above, the annotation indicates a default for all methods of +the declaring class (as well as its subclasses). Alternatively, each method can be +annotated individually. See <> for +further details on which methods Spring considers transactional. Note that a class-level +annotation does not apply to ancestor classes up the class hierarchy; in such a scenario, +inherited methods need to be locally redeclared in order to participate in a +subclass-level annotation. When a POJO class such as the one above is defined as a bean in a Spring context, you can make the bean instance transactional through an `@EnableTransactionManagement` @@ -1441,7 +1447,8 @@ In XML configuration, the `` tag provides similar conveni - <1> + + <1> @@ -1456,7 +1463,7 @@ In XML configuration, the `` tag provides similar conveni TIP: You can omit the `transaction-manager` attribute in the `` -tag if the bean name of the `TransactionManager` that you want to wire in has the name, +tag if the bean name of the `TransactionManager` that you want to wire in has the name `transactionManager`. If the `TransactionManager` bean that you want to dependency-inject has any other name, you have to use the `transaction-manager` attribute, as in the preceding example. @@ -1471,18 +1478,22 @@ programming arrangements as the following listing shows: @Transactional public class DefaultFooService implements FooService { + @Override public Publisher getFoo(String fooName) { // ... } + @Override public Mono getFoo(String fooName, String barName) { // ... } + @Override public Mono insertFoo(Foo foo) { // ... } + @Override public Mono updateFoo(Foo foo) { // ... } @@ -1518,17 +1529,47 @@ Reactive Streams cancellation signals. See the <> secti "Using the TransactionOperator" for more details. +[[transaction-declarative-annotations-method-visibility]] .Method visibility and `@Transactional` -**** -When you use proxies, you should apply the `@Transactional` annotation only to methods -with public visibility. If you do annotate protected, private or package-visible -methods with the `@Transactional` annotation, no error is raised, but the annotated -method does not exhibit the configured transactional settings. If you need to annotate -non-public methods, consider using AspectJ (described later). -**** +[NOTE] +==== +When you use transactional proxies with Spring's standard configuration, you should apply +the `@Transactional` annotation only to methods with `public` visibility. If you do +annotate `protected`, `private`, or package-visible methods with the `@Transactional` +annotation, no error is raised, but the annotated method does not exhibit the configured +transactional settings. If you need to annotate non-public methods, consider the tip in +the following paragraph for class-based proxies or consider using AspectJ compile-time or +load-time weaving (described later). + +When using `@EnableTransactionManagement` in a `@Configuration` class, `protected` or +package-visible methods can also be made transactional for class-based proxies by +registering a custom `transactionAttributeSource` bean like in the following example. +Note, however, that transactional methods in interface-based proxies must always be +`public` and defined in the proxied interface. + +[source,java,indent=0,subs="verbatim,quotes"] +---- + /** + * Register a custom AnnotationTransactionAttributeSource with the + * publicMethodsOnly flag set to false to enable support for + * protected and package-private @Transactional methods in + * class-based proxies. + * + * @see ProxyTransactionManagementConfiguration#transactionAttributeSource() + */ + @Bean + TransactionAttributeSource transactionAttributeSource() { + return new AnnotationTransactionAttributeSource(false); + } +---- + +The _Spring TestContext Framework_ supports non-private `@Transactional` test methods by +default. See <> in the testing +chapter for examples. +==== You can apply the `@Transactional` annotation to an interface definition, a method -on an interface, a class definition, or a public method on a class. However, the +on an interface, a class definition, or a method on a class. However, the mere presence of the `@Transactional` annotation is not enough to activate the transactional behavior. The `@Transactional` annotation is merely metadata that can be consumed by some runtime infrastructure that is `@Transactional`-aware and that @@ -1550,12 +1591,13 @@ the proxy are intercepted. This means that self-invocation (in effect, a method the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with `@Transactional`. Also, the proxy must be fully initialized to provide the expected behavior, so you should not -rely on this feature in your initialization code (that is, `@PostConstruct`). +rely on this feature in your initialization code -- for example, in a `@PostConstruct` +method. -Consider using of AspectJ mode (see the `mode` attribute in the following table) if you -expect self-invocations to be wrapped with transactions as well. In this case, there no -proxy in the first place. Instead, the target class is woven (that is, its byte code is -modified) to turn `@Transactional` into runtime behavior on any kind of method. +Consider using AspectJ mode (see the `mode` attribute in the following table) if you +expect self-invocations to be wrapped with transactions as well. In this case, there is +no proxy in the first place. Instead, the target class is woven (that is, its byte code +is modified) to support `@Transactional` runtime behavior on any kind of method. [[tx-annotation-driven-settings]] .Annotation driven transaction settings @@ -1608,14 +1650,14 @@ NOTE: The `proxy-target-class` attribute controls what type of transactional pro created for classes annotated with the `@Transactional` annotation. If `proxy-target-class` is set to `true`, class-based proxies are created. If `proxy-target-class` is `false` or if the attribute is omitted, standard JDK -interface-based proxies are created. (See <> for a discussion of the -different proxy types.) +interface-based proxies are created. (See <> +for a discussion of the different proxy types.) -NOTE: `@EnableTransactionManagement` and `` looks for +NOTE: `@EnableTransactionManagement` and `` look for `@Transactional` only on beans in the same application context in which they are defined. This means that, if you put annotation-driven configuration in a `WebApplicationContext` for a `DispatcherServlet`, it checks for `@Transactional` beans only in your controllers -and not your services. See <> for more information. +and not in your services. See <> for more information. The most derived location takes precedence when evaluating the transactional settings for a method. In the case of the following example, the `DefaultFooService` class is @@ -1663,8 +1705,8 @@ precedence over the transactional settings defined at the class level. ===== `@Transactional` Settings The `@Transactional` annotation is metadata that specifies that an interface, class, -or method must have transactional semantics (for example, "`start a brand new read-only -transaction when this method is invoked, suspending any existing transaction`"). +or method must have transactional semantics (for example, "start a brand new read-only +transaction when this method is invoked, suspending any existing transaction"). The default `@Transactional` settings are as follows: * The propagation setting is `PROPAGATION_REQUIRED.`