Using @Transactional
In addition to the XML-based declarative approach to transaction configuration, you can use an annotation-based approach. Declaring transaction semantics directly in the Java source code puts the declarations much closer to the affected code. There is not much danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway.
The standard jakarta.transaction.Transactional annotation is also supported as
a drop-in replacement to Spring’s own annotation. Please refer to the JTA documentation
for more details.
|
The ease-of-use afforded by the use of the @Transactional
annotation is best
illustrated with an example, which is explained in the text that follows.
Consider the following class definition:
-
Java
-
Kotlin
// the service class that we want to make transactional
@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) {
// ...
}
}
// the service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
override fun getFoo(fooName: String, barName: String): Foo {
// ...
}
override fun insertFoo(foo: Foo) {
// ...
}
override fun updateFoo(foo: Foo) {
// ...
}
}
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 method visibility 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
annotation in a @Configuration
class. See the
javadoc
for full details.
In XML configuration, the <tx:annotation-driven/>
tag provides similar convenience:
<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- enable the configuration of transactional behavior based on annotations -->
<!-- a TransactionManager is still required -->
<tx:annotation-driven transaction-manager="txManager"/> (1)
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
1 | The line that makes the bean instance transactional. |
You can omit the transaction-manager attribute in the <tx:annotation-driven/>
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.
|
Reactive transactional methods use reactive return types in contrast to imperative programming arrangements as the following listing shows:
-
Java
-
Kotlin
// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
@Override
public Publisher<Foo> getFoo(String fooName) {
// ...
}
@Override
public Mono<Foo> getFoo(String fooName, String barName) {
// ...
}
@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}
@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}
// the reactive service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Mono<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
Note that there are special considerations for the returned Publisher
with regards to
Reactive Streams cancellation signals. See the
Cancel Signals
section under "Using the TransactionalOperator" for more details.
Method visibility and
@Transactional in proxy modeThe If you prefer consistent treatment of method visibility across the different kinds of
proxies (which was the default up until 5.3), consider specifying
The Spring TestContext Framework supports non-private |
You can apply the @Transactional
annotation to an interface definition, a method
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 corresponding
runtime infrastructure which uses that metadata to configure the appropriate beans with
transactional behavior. In the preceding example, the <tx:annotation-driven/>
element
switches on actual transaction management at runtime.
The Spring team recommends that you annotate methods of concrete classes with the
@Transactional annotation, rather than relying on annotated methods in interfaces,
even if the latter does work for interface-based and target-class proxies as of 5.0.
Since Java annotations are not inherited from interfaces, interface-declared annotations
are still not recognized by the weaving infrastructure when using AspectJ mode, so the
aspect does not get applied. As a consequence, your transaction annotations may be
silently ignored: Your code might appear to "work" until you test a rollback scenario.
|
In proxy mode (which is the default), only external method calls coming in through
the proxy are intercepted. This means that self-invocation (in effect, a method within
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 — e.g. in a @PostConstruct 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.
XML Attribute | Annotation Attribute | Default | Description |
---|---|---|---|
|
N/A (see |
|
Name of the transaction manager to use. Required only if the name of the transaction
manager is not |
|
|
|
The default mode ( |
|
|
|
Applies to |
|
|
|
Defines the order of the transaction advice that is applied to beans annotated with
|
The default advice mode for processing @Transactional annotations is proxy ,
which allows for interception of calls through the proxy only. Local calls within the
same class cannot get intercepted that way. For a more advanced mode of interception,
consider switching to aspectj mode in combination with compile-time or load-time weaving.
|
The proxy-target-class attribute controls what type of transactional proxies are
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 Proxying Mechanisms
for a discussion of the different proxy types.)
|
@EnableTransactionManagement and <tx:annotation-driven/> 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 in your services. See MVC 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
annotated at the class level with the settings for a read-only transaction, but the
@Transactional
annotation on the updateFoo(Foo)
method in the same class takes
precedence over the transactional settings defined at the class level.
-
Java
-
Kotlin
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// ...
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// ...
}
}
@Transactional(readOnly = true)
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Foo {
// ...
}
// these settings have precedence for this method
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
override fun updateFoo(foo: Foo) {
// ...
}
}
@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").
The default @Transactional
settings are as follows:
-
The propagation setting is
PROPAGATION_REQUIRED.
-
The isolation level is
ISOLATION_DEFAULT.
-
The transaction is read-write.
-
The transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
-
Any
RuntimeException
orError
triggers rollback, and any checkedException
does not.
You can change these default settings. The following table summarizes the various
properties of the @Transactional
annotation:
Property | Type | Description |
---|---|---|
|
Optional qualifier that specifies the transaction manager to be used. |
|
|
|
Alias for |
|
Array of |
Labels may be evaluated by transaction managers to associate implementation-specific behavior with the actual transaction. |
|
Optional propagation setting. |
|
|
|
Optional isolation level. Applies only to propagation values of |
|
|
Optional transaction timeout. Applies only to propagation values of |
|
|
Alternative for specifying the |
|
|
Read-write versus read-only transaction. Only applicable to values of |
|
Array of |
Optional array of exception types that must cause rollback. |
|
Array of exception name patterns. |
Optional array of exception name patterns that must cause rollback. |
|
Array of |
Optional array of exception types that must not cause rollback. |
|
Array of exception name patterns. |
Optional array of exception name patterns that must not cause rollback. |
See Rollback rules for further details on rollback rule semantics, patterns, and warnings regarding possible unintentional matches for pattern-based rollback rules. |
Currently, you cannot have explicit control over the name of a transaction, where 'name'
means the transaction name that appears in a transaction monitor and in logging output.
For declarative transactions, the transaction name is always the fully-qualified class
name + .
+ the method name of the transactionally advised class. For example, if the
handlePayment(..)
method of the BusinessService
class started a transaction, the
name of the transaction would be: com.example.BusinessService.handlePayment
.
Multiple Transaction Managers with @Transactional
Most Spring applications need only a single transaction manager, but there may be
situations where you want multiple independent transaction managers in a single
application. You can use the value
or transactionManager
attribute of the
@Transactional
annotation to optionally specify the identity of the
TransactionManager
to be used. This can either be the bean name or the qualifier value
of the transaction manager bean. For example, using the qualifier notation, you can
combine the following Java code with the following transaction manager bean declarations
in the application context:
-
Java
-
Kotlin
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}
class TransactionalService {
@Transactional("order")
fun setSomething(name: String) {
// ...
}
@Transactional("account")
fun doSomething() {
// ...
}
@Transactional("reactive-account")
fun doSomethingReactive(): Mono<Void> {
// ...
}
}
The following listing shows the bean declarations:
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="account"/>
</bean>
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>
In this case, the individual methods on TransactionalService
run under separate
transaction managers, differentiated by the order
, account
, and reactive-account
qualifiers. The default <tx:annotation-driven>
target bean name, transactionManager
,
is still used if no specifically qualified TransactionManager
bean is found.
Custom Composed Annotations
If you find you repeatedly use the same attributes with @Transactional
on many different
methods, Spring’s meta-annotation support lets you
define custom composed annotations for your specific use cases. For example, consider the
following annotation definitions:
-
Java
-
Kotlin
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx
The preceding annotations let us write the example from the previous section as follows:
-
Java
-
Kotlin
public class TransactionalService {
@OrderTx
public void setSomething(String name) {
// ...
}
@AccountTx
public void doSomething() {
// ...
}
}
class TransactionalService {
@OrderTx
fun setSomething(name: String) {
// ...
}
@AccountTx
fun doSomething() {
// ...
}
}
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.