Most users of the Spring Framework choose declarative transaction management. This option has the least impact on application code and is therefore most consistent with the idea of a non-invasive lightweight container.

Declarative transaction management in the Spring Framework is made possible by aspect-oriented programming (AOP) in Spring. However, since the transactional aspect code is distributed with the Spring Framework distribution and can be used as a template, an understanding of AOP concepts is generally not required to effectively use this code.

Declarative transaction management in the Spring Framework is similar to EJB's CMT , since it is possible to determine the transaction logic (or lack thereof) down to the level of individual methods. If necessary, you can call setRollbackOnly() in the context of a transaction. The differences between the two types of transaction management are as follows:

  • Unlike EJB's CMT, which is tied to JTA, Spring Framework's declarative transaction management works in any environment. It can work with JTA transactions or local transactions using JDBC, JPA or Hibernate by adjusting the configuration files.

  • You can apply declarative transaction management in the Spring Framework to any class, not just to special classes such as EJB classes.

  • Spring Framework offers declarative rollback rules that have no analogues in EJB. There is both programmatic and declarative support for rollback rules.

  • Spring Framework allows you to customize transaction logic using AOP. For example, you can implement custom logic for working in the event of a transaction rollback. You can also add custom Advice along with transactional Advice. When using CMT from an EJB, you cannot influence container transaction management except by using setRollbackOnly().

  • Spring Framework does not support propagating transaction contexts across remote calls like high-end application servers do. If you need this functionality, we recommend using EJB. However, think carefully before using such a function because, in general, transactions should not be covered by remote calls.

The concept of rollback rules is very important. These rules allow you to specify which exceptions (and thrown events) should result in an automatic rollback. You can set this declaratively, in the configuration, rather than in Java code. So while you can still call setRollbackOnly() on a TransactionStatus object to roll back the current transaction, it is often better to set the rule that the exception is MyApplicationExceptionException should always result in a rollback. A significant advantage of this option is that business objects will not depend on the transaction infrastructure. For example, they typically do not require importing the Spring transaction APIs or other Spring APIs.

Although the default EJB container logic automatically rolls back a transaction upon a system exception (typically a runtime exception), EJB CMT does not rolls back a transaction automatically when an application exception (that is, a checked exception other than java.rmi.RemoteException) occurs. Although Spring's default logic for declarative transaction management follows the EJB convention (rollback occurs automatically only for unchecked exceptions), it is often useful to customize this logic.

Basic understanding of the Spring Framework's declarative transaction implementation

It's not enough to simply say that you need to mark your classes with the @Transactional annotation, add the @EnableTransactionManagement annotation to your configuration and expect you to understand how it all works. To provide a more in-depth understanding, this section describes the internals of the Spring Framework's declarative transaction framework in the context of transaction-related issues.

The most important concepts to understand regarding the Spring Framework's declarative transaction support are: that this support is provided by AOP proxy and that transactional Advice is metadata driven (currently XML based or annotations). Combining AOP with transactional metadata produces an AOP proxy that uses a TransactionInterceptor in conjunction with a corresponding TransactionManager implementation to manage transactions around method calls.

AOP in Spring is discussed in the section by AOP.

TransactionInterceptor in the Spring Framework provides transaction management for imperative and reactive programming models. The interceptor determines the desired type of transaction control by checking the return type of the method. Methods that return a reactive type, such as Publisher or Flow (or a subtype thereof) in Kotlin, are suitable for managing reactive transactions. All other return types, including void, use the code execution path for imperative transaction management.

The specifics of transaction management affect which transaction manager is required. Imperative transactions require a PlatformTransactionManager, and reactive transactions use a ReactiveTransactionManager implementation.

Annotation @Transactional typically works with thread-bound transactions managed by the PlatformTransactionManager, opening a transaction for all data access operations on the currently executing thread. Note: This does not apply to newly started threads within a method.

A reactive transaction managed by a ReactiveTransactionManager uses the context from Reactor instead of thread-local attributes. As a consequence, all participating data access operations must be executed in the same context from Reactor in the same reactive pipeline.

The following figure shows a conceptual representation of a method call for a transactional proxy:

An example of a declarative transaction implementation

Consider the following interface and its corresponding implementation. This example uses the Foo and Bar classes as placeholders so you can focus on using transactions without being distracted by a specific domain model. For the purposes of this example, the fact that the DefaultFooService class throws instances of UnsupportedOperationException in the body of each implemented method is positive. This operating logic allows you to understand how transactions are created and then rolled back in response to an instance of UnsupportedOperationException. The following listing shows the FooService interface:

Java
// service interface that we want to make transactional
package x.y.service;
public interface FooService {
    Foo getFoo(String fooName);
    Foo getFoo(String fooName, String barName);
    void insertFoo(Foo foo);
    void updateFoo(Foo foo);
}
Kotlin
// service interface that we want to make transactional
package x.y.service
interface FooService {
    fun getFoo(fooName: String): Foo
    fun getFoo(fooName: String, barName: String): Foo
    fun insertFoo(foo: Foo)
    fun updateFoo(foo: Foo)
}

The following example shows the implementation of the previous interface:

Java
package x.y.service;
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) {
        // ...
    }
}
Kotlin
package x.y.service
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) {
        // ...
    }
}

Assume that the first two methods of the FooService interface, getFoo(String) and getFoo(String, String), must be executed in the context of some transaction with read-only semantics, and the other methods, insertFoo(Foo) and updateFoo(Foo), must be executed in the context of a transaction with read-write semantics. The following configuration is detailed in the next few paragraphs:

<!-- 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"/>
    <!-- transactional Advice (what is "happening"; see <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- transactional semantics...... -->
        <tx:attributes>
            <!-- all methods starting with "get" are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use default transaction parameters (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!-- make sure that the above transactional Advice is executed whenever
        an operation defined by the FooService interface is executed -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>
    <!-- don't forget about DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    <!-- Likewise, don’t forget about the TransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- other definitions <bean/--> -->
</beans>

Examine the previous configuration. The assumption is that you need to make the service object, the fooService bean, transactional. The transaction semantics that need to be applied are contained in the <tx:advice/> definition. The definition of <tx:advice/> states: "all methods beginning with get must be executed in the context of a read-only transaction, and all other methods must be executed with default transaction semantics." The transaction-manager attribute of the <tx:advice/> tag is indicated in the name of the TransactionManager bean that will manage transactions (in this case it is the bean txManager).

You can omit the transaction-manager attribute in the transaction Advice (<tx:advice/>), if the name of the bean TransactionManager that you want to connect is transactionManager. If the TransactionManager bean you need to associate has any other name, then you must use the transaction-manager attribute explicitly, as in the previous example.

The <aop:config/> definition allows transactional recommendations defined by the txAdvice bean to be guaranteed to run at appropriate points in the program. First, a slice is defined that is mapped to perform any operation defined in the FooService(fooServiceOperation) interface. The slice is then bound to txAdvice using Advisor. The result shows that when fooServiceOperation is executed, the Advice defined by txAdvice begins to be executed.

The expression defined in the <aop:pointcut/>element is a slice expression from AspectJ. For more information about slicing expressions in Spring, see the section on AOP.

It is often necessary to make the entire service layer transactional. The best way to do this is to modify the slice expression to match any service-level operation. The following example shows how to do this:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
The previous example assumes that all service interfaces are defined in package x.y.service. For more information, see the AOP section.

Now that we've analyzed configuration, then you may be wondering, “What exactly does all this configuration do?”

The configuration shown earlier is used to create a transactional proxy around the object that is created from the bean definition fooService. The proxy is configured using transactional Advice in such a way that when the corresponding method is called on the proxy, the transaction is started, suspended, marked as read-only, and so on, depending on the transaction configuration associated with that method. Consider the following program, which does a test run of the configuration shown earlier:

Java
public final class Boot {
    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
        FooService fooService = ctx.getBean(FooService.class);
        fooService.insertFoo(new Foo());
    }
}
Kotlin
import org.springframework.beans.factory.getBean
fun main() {
    val ctx = ClassPathXmlApplicationContext("context.xml")
    val fooService = ctx.getBean<FooService>("fooService")
    fooService.insertFoo(Foo())
}

The output of the previous program should look like this (the Log4J output and stack trace from the UnsupportedOperationException thrown by the insertFoo(..) method of the DefaultFooService class were truncated for clarity):

<!-- Spring container starts... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors
<!-- DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... insertFoo(..) method is now called on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- this is where the transaction Advice comes into play... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
<!-- method insertFoo(..) from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable
[java.lang.UnsupportedOperationException]
<!-- and transaction is rolled back (by default, RuntimeException instances result in a rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection
[org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource
Exception in thread "main" java.lang .UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace items removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

To use reactive transaction management, code must use reactive types .

Spring Framework uses the ReactiveAdapterRegistry to determine whether the return type of a method is reactive.

The following listing shows a modified version of the previously used FooService, but this time the code uses reactive types:

Java
// reactive interface the service we want to make transactional
package x.y.service;
public interface FooService {
    Flux<Foo> getFoo(String fooName);
    Publisher<Foo> getFoo(String fooName, String barName);
    Mono<Void> insertFoo(Foo foo);
    Mono<Void> updateFoo(Foo foo);
}
Kotlin
// the reactive service interface that we want to make transactional
package x.y.service
interface FooService {
    fun getFoo(fooName: String): Flow<Foo>
    fun getFoo(fooName: String, barName: String): Publisher<Foo>
    fun insertFoo(foo: Foo) : Mono<Void>
    fun updateFoo(foo: Foo) : Mono<Void>
}

The following example shows the implementation of the previous interface:

Java
package x.y.service;
public class DefaultFooService implements FooService {
    @Override
    public Flux<Foo> getFoo(String fooName) {
        // ...
    }
    @Override
    public Publisher<Foo> getFoo(String fooName, String barName) {
        // ...
    }
    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }
    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}
Kotlin
package x.y.service
class DefaultFooService : FooService {
    override fun getFoo(fooName: String): Flow<Foo> {
        // ...
    }
    override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
        // ...
    }
    override fun insertFoo(foo: Foo): Mono<Void> {
        // ...
    }
    override fun updateFoo(foo: Foo): Mono<Void> {
        // ...
    }
}

Imperative and reactive transaction management have the same semantics for defining transaction boundaries and transaction attributes. The main difference between imperative and reactive transactions is the deferred nature of the latter. To begin, the TransactionInterceptor decorates the reactive type's return type with a transactional operator and clears the transaction. Therefore, calling a transactional reactive method transfers actual transaction control to the subscription type, which enables the reactive type's processing.

Another aspect of reactive transaction control involves data escaping, which is a natural consequence of the programming model.

Returns the values of imperative transaction methods are returned from transactional methods when the method completes successfully, so that partially computed results do not exit the method's closure.

Reactive transaction methods return a reactive wrapper function type, which is a sequence of evaluations along with a promise to begin and complete the calculations.

Publisher can produce data while the transaction is in progress, but not necessarily completed. Therefore, methods that depend on the successful completion of the entire transaction must ensure that the calling code completes and buffers the results.

Rolling back a declarative transaction

B The previous section laid out the basics of how to declaratively set transaction parameters for classes (usually service-layer classes) in your application. This section describes how to manage transaction rollback in a simple, declarative way in an XML configuration. For more information on declaratively managing rollback semantics using the @Transactional annotation, see @Transactional annotation parameters.

The recommended way to instruct the Spring Framework transactional framework to rollback a transaction is to throw an Exception from the code, which is currently running in the context of a transaction. The Spring Framework transactional framework code intercepts any unhandled Exception as it comes up the call stack and determines whether the transaction should be marked for rollback.

In the default configuration, the Spring Framework transactional framework code Marks a transaction for rollback only in the case of unchecked run-time exceptions. That is, if the exception thrown is an instance or subclass of RuntimeException. (Instances of Error also cause a rollback by default.) Checked exceptions that are thrown from a transactional method do not result in a rollback in the default configuration.

You can configure which Exception types mark a transaction for rollback, including checked exceptions, by specifying rollback rules.

Rollback rules

Rollback rules determine whether a transaction should be rolled back when a specific exception occurs, and these rules are based on patterns. The pattern can be a fully qualified class name or a substring of the fully qualified class name for the exception type (which must be a subclass of Throwable), with no wildcard support currently available. For example, the value "javax.servlet.ServletException" or "ServletException" will match javax.servlet.ServletException and its subclasses.

Rollback rules can be configured in XML using the rollback-for and no-rollback-for attributes, which allow patterns to be specified as strings. When using the annotation @Transactional rollback rules can be configured using the rollbackFor/noRollbackFor and rollbackForClassName/noRollbackForClassName attributes, which allow patterns to be specified as Class references or strings, respectively. If the exception type is specified as a class reference, its full name will be used as a template. Therefore, the annotation @Transactional(rollbackFor = example.CustomException.class) is equivalent to the annotation @Transactional(rollbackForClassName = "example.CustomException").

You'll need to carefully consider how specific the template is and whether you need to include package information (this is optional). For example, "Exception" will match almost anything and will probably hide other rules. "java.lang.Exception" would be appropriate if "Exception" defined a rule for all exceptions to be checked. By using more unique exception names, such as "BaseBusinessException", you will likely not need to use the fully qualified class name for the exception pattern.

Furthermore, rollback rules may cause unintended matches for exceptions of the same name and nested classes. This is because a thrown exception is considered to match a given rollback rule if the name of the thrown exception contains the exception template configured for that rollback rule. For example, if a rule is configured to match com.example.CustomException, it will match an exception named com.example.CustomExceptionV2 (an exception from the same package as CustomException, but with an additional suffix) or an exception named com.example.CustomException$AnotherException (an exception declared as a nested class of CustomException).

The following XML fragment demonstrates how to set up a fallback for a checked, application-specific Exception by providing a exception pattern via the rollback-for attribute:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

If you do not want the transaction to be rolled back when an exception is thrown, you can also set "no rollback" rules. The following example instructs the Spring Framework transaction framework to commit the corresponding transaction even in the case of an unhandled InstrumentNotFoundException exception:

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

If the Spring Framework transaction framework catches the exception and looks to the configured rollback rules to determine whether to mark the transaction for rollback, it takes precedence becomes the strictest rule. Thus, in the case of the following configuration, any exception except InstrumentNotFoundException leads to the rollback of the corresponding transaction:

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>

You can also specify a mandatory rollback programmatically. Although simple, this process is quite invasive and tightly couples your code to the Spring Framework's transaction infrastructure. The following example shows how to programmatically specify a mandatory rollback:

Java
public void resolvePosition() {
    try {
        // some business logic...
    } catch ( NoProductInStockException ex) {
        // start the rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
Kotlin
fun resolvePosition() {
    try {
        // some business logic...
    } catch (ex: NoProductInStockException) {
        // start the rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

It is highly recommended to use a declarative approach to rollback if at all possible. Software rollback is available if absolutely necessary, but its use goes against the principles of a pure POJO-based architecture.

Configuring different transaction semantics for different beans

Consider a scenario in which there are multiple objects service layer, and you need to apply completely different transactional configurations to each of them. This can be done by defining separate <aop:advisor/> elements with different values for the pointcut and advice-ref attributes.

For comparison, first assume that all service layer classes are defined in the root package x.y.service. To ensure that all beans that are instances of classes defined in this package (or in subpackages) and whose names end in Service have a default transactional configuration, you can write the following:

<?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">
    <aop:config>
        <aop:pointcut id="serviceOperation"
                expression="execution(* x.y.service..*Service.*(..))"/>
        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
    </aop:config>
    <!-- these two beans will be transactional... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>
    <!-- ... and these two bins will not... -->
    <bean id="anotherService" class="org.xyz.SomeService"/>
    <!-- (not in the right package) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/>
    <!-- (doesn't end in 'Service') -->
    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!-- other transaction infrastructure beans, such as TransactionManager, are omitted... -->
</beans>

The following example shows how to configure two different beans with completely different transaction parameters:

<?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">
    <aop:config>
        <aop:pointcut id="defaultServiceOperation"
                expression="execution(* x.y.service.*Service.*(..))"/>
        <aop:pointcut id="noTxServiceOperation"
                expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
    </aop:config>
    <!-- this bean will be transactional (see the "defaultServiceOperation" slice) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- this bean will also be transactional, but with completely different transactional parameters -->
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
            </tx:attributes>
        </tx:advice>
    <!-- other transaction infrastructure beans, such as TransactionManager, are omitted... -->
</beans>

Settings <tx:advice/>

This section briefly describes the various transactional parameters that can be set using the <tx:advice/> tag. The default <tx:advice/> parameters are as follows:

  • Propagation parameter is REQUIRED.

  • Isolation level is DEFAULT .

  • The transaction is performed on a read-write basis.

  • The default transaction timeout is equal to the default timeout of the base transaction system or missing if timeout values are not supported.

  • Any RuntimeException causes a rollback, and any checked Exception - no.

You can change these default settings. The following table shows the various attributes of the <tx:method/> tags that are nested within the <tx:advice/> and <tx:attributes/> tags:

Table 1. Parameters <tx:method/>
Attribute Required? Default Description

name

Yes

Names of methods to which transaction attributes should be associated. The wildcard character (*) can be used to associate the same transaction attribute parameters with multiple methods (for example, get*, handle*, on* Event and so on).

propagation

No

REQUIRED

Logic of operation when propagating transactions.

isolation

No

DEFAULT

Transaction isolation level. Applies only to REQUIRED or REQUIRES_NEW distribution settings.

timeout

No

-1

Transaction timeout (seconds). Applies only to REQUIRED or REQUIRES_NEW distributions.

read-only

No

false

Transaction for " read-write vs. read-only transaction. Applies only to REQUIRED or REQUIRES_NEW.

rollback-for

None

List of Exception instances that cause a rollback, separated by commas. For example, com.foo.MyBusinessException,ServletException.

no-rollback-for

No

List of Exception instances that do not throw rollback, separated by commas. For example, com.foo.MyBusinessException,ServletException.

Usage annotations @Transactional

In addition to the declarative approach to XML-based transaction configuration, you can use an annotation-based approach. Declaring transaction semantics directly in the Java source code makes the declarations much closer to the affected code. There is little danger of overcoupling because code intended for transactional use is almost always deployed that way.

Standard annotation javax.transaction.Transactional is also supported as a replacement for Spring's native annotation. More information can be found in the JTA 1.2 documentation.

The ease of use of the @Transactional annotation is best illustrated by an example, which is explained in the text below. Consider the following class definition:

Java
// the service class 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) {
        // ...
    }
}
Kotlin
// service class 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 the annotation, as described above, specifies the default value for all methods of the declaring class (as well as its subclasses). In addition, each method can be annotated separately. See "Method visibility and @Transactional" for more information about which methods Spring considers transactional. Note that class-level annotation does not extend to ancestor classes higher in the class hierarchy; in this case, the inherited methods must be locally redeclared to participate in the subclass-level annotation.

If a POJO class like the one above is defined as a bean in the Spring framework, you can make the bean instance transactional using the @EnableTransactionManagement annotation in a class annotated with @Configuration. For details, see javadoc.

In an XML configuration, the <tx:annotation-driven/> tag provides a similar mechanism:

<!-- from 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 we want to make transactional -->
        <bean id="fooService" class="x.y.service.DefaultFooService"/>
        <!-- activate the configuration of transaction logic based on annotations -->
        <!-- TransactionManager still needed -->
        <tx:annotation-driven transaction-manager="txManager"/>
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- (this dependency is defined elsewhere) -->
            <property name="dataSource" ref="dataSource"/>
        </bean>
            <!-- other definitions <bean/--> -->
</beans>
  1. A string that makes the bean instance transactional.
You can omit the transaction-manager attribute in the <tx:annotation-driven/> tag if the TransactionManager bean you want to connect to is named transactionManager. If the TransactionManager bean you need to inject has any other name, you must use the transaction-manager attribute as in the previous example.

Reactive Transactional Methods use reactive return types as opposed to imperative programming mechanisms, as shown in the following listing:

Java
// 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) {
        // ...
    }
}
Kotlin
// reactive service class, which 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 the returned Publisher has special considerations regarding Reactive Streams cancellation signals. For more information, see "Cancel Signals" in the "Using the TransactionalOperator" section.

Method visibility and @Transactional annotation

If you are using transactional proxies with the standard Spring configuration, then the @Transactional annotation should only be applied to methods with public visibility. If you mark protected, private methods, or package-scoped methods with the @Transactional annotation, no error will occur, but the annotated method will not return the configured transaction parameters. If you need to annotate non-public methods, see the tip in the next paragraph which describes class-based proxies, or use AspectJ-based load-time or compile-time binding (described later).

When using the @EnableTransactionManagementannotation in a class marked with the @Configuration annotation, protected methods or package-scoped methods can also be made transactional for a class-based proxy by registering a custom bean transactionAttributeSource, as shown in the example below. Note, however, that transactional methods in an interface-based proxy must always be public and defined in the proxied interface.

/**
    * Register a custom AnnotationTransactionAttributeSource using the
    * publicMethodsOnly flag set to false to enable support for
    * protected methods and methods with package scope,
    * marked with the @Transactional annotation, in
    * class-based proxies.
    *
    * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
*/ @Bean
TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}

Spring TestContext Framework by default supports non-private test methods annotated with @Transactional. For examples, see "Transaction Management" in the testing chapter.

The @Transactional annotation can be applied to an interface definition, an interface method, a class definition, or a class method. However, the mere presence of the @Transactional annotation is not enough to activate the transactional logic of the work. The @Transactional annotation is simply metadata that can be consumed by some runtime infrastructure that supports the @Transactional annotation and can use the metadata to configure appropriate beans with transactional operating logic. In the previous example, the <tx:annotation-driven/> element toggles transactional logic.

The Spring team recommends annotating The @Transactional annotation only applies to concrete classes (and methods of concrete classes), and does not annotate interfaces. You can, of course, annotate an interface (or an interface method) with the @Transactional annotation, but that will only work the way it would normally work if you were using an interface-based proxy. The fact that Java annotations do not inherit from interfaces means that if you use a class-based proxy (proxy-target-class="true") or a binding-based aspect (mode=" aspectj"), transaction parameters will not be recognized by the proxying and binding infrastructure, and the object will not be wrapped in a transactional proxy.
In proxy mode (which is the default) only external method calls coming through the proxy are intercepted. This means that a self-call (essentially a method inside a target object calling another method on the target object) does not result in an actual transaction at runtime, even if the method being called is marked with the @Transactional annotation. Additionally, the proxy must be fully initialized to perform the expected logic, so you should not rely on this function in initialization code - for example, in a method annotated with @PostConstruct.

Consider the ability to use AspectJ mode (see the mode attribute in the following table) if self-calls are expected to also be wrapped in transactions. In this case, the proxy will generally not be in first place. Instead, the target class is modified (that is, its bytecode is modified) to begin supporting the runtime logic of the @Transactional annotation for a method of any kind.

Table 2. Annotation-based transaction settings
XML Attribute Annotation Attribute Default Description

transaction-manager

N/A (see javadoc at TransactionManagementConfigurer )

transactionManager

The name of the transaction manager to use. Required only if the transaction manager name is not transactionManager, as in the previous example.

mode

mode

proxy

The default mode (proxy) handles annotated beans for proxying using the Spring AOP framework (following the proxy semantics (described earlier) that apply only to method calls , coming through a proxy). The alternative mode (aspectj) instead binds the affected classes to a transaction aspect based on Spring's AspectJ, modifying the bytecode of the target class to apply to a method call of either kind. AspectJ-based linking requires spring-aspects.jar to be present in the classpath, and linking to be enabled at load time (or linking at compile time). (For more information on how to configure boot-time binding, see "Spring Configuration").

proxy-target-class

proxyTargetClass

false

Applies only in proxy mode. Controls what type of transactional proxies are created for classes annotated with @Transactional. If the proxy-target-class attribute is set to true, class-based proxies are created. If proxy-target-class is false or the attribute is omitted, then standard proxies are created based on the JDK interface. (For a detailed description of the different types of proxies, see "Proxy Mechanisms").

order

order

Ordered.LOWEST_PRECEDENCE

Defines the order of transactional Advice that is applied to beans marked with the @Transactional annotation. (For more information about the rules associated with AOP Advice ordering, see "Advice Ordering") . The absence of a specified order means that the AOP subsystem will determine the Advice order.

Mode Advice supplies the default for processing @Transactional annotations - proxy, which allows you to intercept calls only through a proxy. Local calls within the same class cannot be intercepted in the same way. For a more advanced interception mode, consider switching to aspectj mode in combination with compile-time or load-time binding.
The proxy-target-class attribute controls what type of transactional proxies are created for classes annotated with @Transactional. If proxy-target-class is true, class-based proxies are created. If proxy-target-class is false or the attribute is omitted, standard proxies are created based on the JDK interface. (For a description of the different types of proxies, see "Proxy Mechanisms").
@EnableTransactionManagement and <tx:annotation-driven/> look for @Transactional only for beans in the same application context in which they are defined. This means that if you put an annotation-driven configuration in the WebApplicationContext for the DispatcherServlet, it will only check for beans with the @Transactional annotation in your controllers, but not in your services. For more information, see the section on MVC.

When calculating the transactional parameters of a method, the most derived location takes precedence. In the following example, the DefaultFooService class is annotated at the class level with read-only transaction settings, but the @Transactional annotation on the updateFoo(Foo) method is in the same class takes precedence over transaction settings defined at the class level.

Java
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
    public Foo getFoo( String fooName) {
        // ...
    }
    // these settings take precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // ...
    }
}
Kotlin
@Transactional(readOnly = true)
class DefaultFooService : FooService {
    override fun getFoo(fooName: String): Foo {
        // ...
    }
    // these settings take precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    override fun updateFoo(foo: Foo) {
        // ...
    }
}

Parameters of the @Transactional

The @Transactional annotation is metadata indicating that the interface, class or the method must have transactional semantics (for example, "when this method is called, begin an entirely new transaction in read-only mode, suspending all existing transactions"). The default @Transactional parameters are:

  • The propagation parameter is PROPAGATION_REQUIRED.

  • Isolation level is ISOLATION_DEFAULT.

  • The transaction is performed on a read-write basis.

  • The default transaction timeout is equal to the default timeout of the underlying transaction system, or not if timeout values are not supported.

  • Any RuntimeException or Error cause a rollback, but any checked Exception does not.

You can change these default settings. The following table shows the various properties of the @Transactional annotation:

@Transactional annotation parameters
Property Type Description

value

String

An optional qualifier that specifies the transaction manager to use.

transactionManager

String

An alias for value.

label

An array of String labels to add a meaningful description to a transaction.

Labels can be computed by transaction managers to associate a transaction-specific implementing the logic for working with the actual transaction.

propagation

enum: Propagation

Additional distribution parameter.

isolation

enum: Isolation

Additional isolation level. Applies only to REQUIRED or REQUIRES_NEW propagation values.

timeout

int (in granularity seconds)

Optional transaction timeout. Applies only to REQUIRED or REQUIRES_NEW propagation values.

timeoutString

String (in seconds of granularity)

Alternative for setting timeout in seconds as a String value - for example, as a placeholder.

readOnly

boolean

Transaction in read-write mode versus transaction in read-only mode. Applies only to REQUIRED or REQUIRES_NEW values.

rollbackFor

An array of Class objects that must be derived from Throwable.

An optional array of exception types that should cause a rollback.

rollbackForClassName

An array of exception name patterns.

An optional array of exception name patterns that should trigger a rollback.

noRollbackFor

Array of Class objects that must be derived from Throwable .

An optional array of exception types that should not cause a rollback.

noRollbackForClassName

An array of exception name patterns.

An optional array of exception name patterns that are not should cause a rollback.

Learn more about the semantics of rollback rules, patterns and warnings For possible unintended matches, see "Rollback Rules".

Currently, you cannot explicitly control the transaction name, where "name" refers to the transaction name that appears in the transaction monitor, if applicable (for example, WebLogic Transaction Monitor), and in the logged output. For declarative transactions, the transaction name is always the fully qualified class name + . + method name of the transactionally equipped Advice class. For example, if the handlePayment(..) method of the BusinessService class starts a transaction, the transaction name would be: com.example.BusinessService.handlePayment.

Using multiple transaction managers using the @Transactional

annotation

Most Spring applications only need one transaction manager, but there may be situations where you need multiple independent transaction managers in the same application . You can use value or the transactionManager attribute of the @Transactional annotation to optionally specify the identifier of the TransactionManager to use. This can be either the name of the bean or the value of the transaction manager bean qualifier. For example, using qualifier notation, you can combine the following Java code with the following transaction manager bean declarations in the application context:

Java
public class TransactionalService {
    @Transactional("order")
    public void setSomething(String name) { ... }
    @Transactional("account")
    public void doSomething() { ... }
    @Transactional("reactive-account")
    public Mono<Void> doSomethingReactive() { ... }
}
Kotlin
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.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="order"/>
    </bean>
    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="account"/>
    </bean>
    <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
        ...
        <qualifier value="reactive-account"/>
    </bean>

In this case, separate methods for TransactionalService operate under the control of separate transaction managers, differing in qualifiers order, account and reactive-account. The default target bean name <tx:annotation-driven>, transactionManager, is still used if a specially qualified TransactionManager bean cannot be found.

Custom Composite Annotations

If you find that the same attributes with the @Transactional annotation are used repeatedly in many different methods, Spring's support for meta annotations will allow you to define custom compound annotations for specific use cases. As an example, consider the following annotation definition:

Java
@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 {
}
Kotlin
@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 previous annotations allow us to write the example from the previous section as follows:

Java
public class TransactionalService {
    @OrderTx
    public void setSomething(String name) {
        // ...
    }
    @AccountTx
    public void doSomething() {
        // ...
    }
}
Kotlin
class TransactionalService {
    @OrderTx
    fun setSomething(name: String) {
        // ...
    }
    @AccountTx
    fun doSomething() {
        // ...
    }
}

In the previous example, we used the syntax to define the transaction manager qualifier and transactional labels, but we could also add propagation logic, rollback rules, timeouts, and other functions.

Transaction Propagation

This section describes the specific semantics of transaction propagation in Spring. Please note that this section is not a proper introduction to the topic of transaction propagation. Rather, it details the semantics of transaction propagation in Spring.

For Spring-managed transactions, be aware of the distinction between physical and logical transactions, and how the propagation option is applied to account for that distinction.

Understanding PROPAGATION_REQUIRED

PROPAGATION_REQUIRED ensures that a physical transaction is executed either locally for the current scope availability if the transaction does not already exist, or by being in an existing "external" transaction defined for a larger availability area. This is a perfectly acceptable default in a typical single-thread call stack organization (for example, a service facade that delegates to multiple repository methods, where all underlying resources must be present in a service-level transaction).

By default, the participating transaction joins the characteristics of the outer availability scope, silently ignoring the local isolation level, timeout value, or read-only flag (if any). Consider switching the validateExistingTransactions flag to true for your transaction manager if you want isolation level declarations to be unavailable when participating in an existing transaction with a different isolation level. This mode also rejects read-only mode mismatches (that is, if an internal read-write transaction attempts to enter an outer read-only availability scope).

If the propagation parameter is set to PROPAGATION_REQUIRED, a logical transaction area is created for each method to which this parameter is applied. Each such logical transaction area can define the rollback-only status individually, with the external transaction area being logically independent of the internal transaction area. In the case of the standard PROPAGATION_REQUIRED operating logic, all these availability areas are mapped to the same physical transaction. Thus, a rollback-only token set in the internal transaction availability scope affects the ability of the outer transaction to commit.

However, in the case in which the internal transaction availability scope sets the rollback-only marker, the outer transaction the transaction does not make the rollback decision on its own, so the rollback (silently caused by the scope of internal transactions) is unexpected. At this point, the corresponding UnexpectedRollbackException is thrown. This is the expected logic of operation, so the code calling the transaction cannot misbehave by thinking that a commit has been made when in fact it has not. Thus, if an internal transaction (that the external calling code is unaware of) silently marks the transaction as being rolled back, the external calling code will still cause the commit. The external caller should receive an UnexpectedRollbackException to clearly indicate that a rollback was performed instead.

Understanding PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEW, unlike PROPAGATION_REQUIRED, always uses an independent physical transaction for each affected transaction availability region, never participating in an existing one transaction intended for the outer availability scope. In this design, the transactions underlying the resource are distinct and therefore can be committed or rolled back independently of each other, with the outer transaction unaffected by the rollback status of the inner transaction and locks on the inner transaction being released immediately upon completion. This independent inner transaction can also declare its own isolation level, timeout, and read-only parameters and not inherit the characteristics of the outer transaction.

Understanding PROPAGATION_NESTED

PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that can be rolled back to. These partial rollbacks allow the availability region of the inner transaction to initiate a rollback for its availability region, while the outer transaction can continue the physical transaction even though some operations have been rolled back. This option typically maps to JDBC savepoints, so it only works with JDBC resource transactions. See DataSourceTransactionManager from Spring.

Providing transactional operations with advice

Suppose you need to perform both transactional operations and some basic profiling Advice. How can this be done in the context of <tx:annotation-driven/>?

If the updateFoo(Foo) method is called, then you can expect the following actions:

  • The configured profiling aspect is launched.

  • The transactional Advice begins to execute.

  • The method for the Advice-equipped object begins to execute.

  • The transaction is committed.

  • The profiling aspect reports the exact duration of the entire calling a transactional method.

We don't particularly touch on AOP in this chapter (except for its application to transactions). See the section on AOP for detailed coverage of AOP configuration and AOP in general.

The following code shows the simple profiling aspect discussed earlier:

Java
package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;
public class SimpleProfiler implements Ordered {
    private int order;
    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    // this method is Advice "instead of"
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}
Kotlin
class SimpleProfiler : Ordered {
    private var order: Int = 0
    // allows us to control the ordering of advice
    override fun getOrder(): Int {
        return this.order
    }
    fun setOrder(order: Int) {
        this.order = order
    }
    // this method is Advice "instead of"
    fun profile(call: ProceedingJoinPoint): Any {
        var returnValue: Any
        val clock = StopWatch(javaClass.name)
        try {
            clock.start(call.toShortString())
            returnValue = call.proceed()
        } finally {
            clock.stop()
            println(clock.prettyPrint())
        } return returnValue
    }
}

Advice ordering is controlled using the Ordered interface. For more information about organizing Advice, see "Ordering Advice".

Next the configuration creates a bean fooService, to which profiling and transactional aspects are applied in the required order:

<?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">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- this is an aspect of -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- is executed before the transactional Advice (therefore the sequence number is lower) -->
        <property name="order" value="1"/>
    </bean>
    <tx:annotation-driven transaction-manager="txManager" order="200"/>
    <aop:config>
            <!-- this Advice is executed instead of the transactional Advice... -->
            <aop:aspect id="profilingAspect" ref="profiler">
                <aop:pointcut id="serviceMethodWithReturnValue"
                        expression="execution(!void x.y..*Service.*(..))"/>
                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
            </aop:aspect>
    </aop:config>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

Any number of additional aspects can be configured in a similar manner.

The following example creates the same set of specified values, as in the previous two examples, but uses a purely declarative XML approach:

<?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">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <!-- profiling Advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- is executed before the transactional Advice (therefore the sequence number is lower) -->
        <property name="order" value="1"/>
    </bean>
    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        <!-- runs after the profiling Advice (cf. queue attribute) -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
        <!-- the priority value is higher than that of the profiling aspect -->
        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                    expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!-- other <bean/> definitions such as DataSource and TransactionManager -->
</beans>

The result of the previous configuration is the bean fooService, to which the profiling and transactional aspects are applied in that order. If you want the profiling Advice to be executed after the transactional Advice at the input and before the transactional Advice at the output, you can change the value of the order property of the profiling aspect bean so that it is higher than the order value of the transactional Advice.

Additional aspects can be configured in a similar manner.

Using the @Transactional annotation using AspectJ

You can also use the @Transactional annotation support tools from the Spring Framework outside of a Spring container using an AspectJ aspect. To do this, first mark your classes (and optionally class methods) with the @Transactional annotation, and then bind (link) your application to org.springframework.transaction.aspectj.AnnotationTransactionAspect defined in the spring-aspects.jar file. You also need to configure the aspect using the transaction manager. You can use the Spring Framework IoC container to inject dependencies into an aspect. The easiest way to configure the transaction management aspect is to use the <tx:annotation-driven/> element and set the mode attribute to aspectj as described See "Using the @Transactional" annotation. Since we're focusing on applications that run outside of the Spring container, we'll show you how to do this programmatically.

Before continuing, you may want to check out the "Using the @Transactional" annotation and "AOP" respectively.

The following example shows how to create a transaction manager and configure AnnotationTransactionAspect to use it:

Java
// construct the corresponding transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
// configure AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
Kotlin
// construct the appropriate transaction manager
val txManager = DataSourceTransactionManager(getDataSource())
// configure AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().transactionManager = txManager
When When using this aspect, you must annotate the implementation class (or the methods within that class, or both), rather than the interface (if any) that the class implements. AspectJ follows the Java rule that annotations for interfaces are not inherited.

The @Transactional annotation on a class defines the default transaction semantics for executing any public method in the class.

The @Transactional annotation on a method in a class overrides the default transaction semantics specified by the class annotation (if present). You can annotate any method, regardless of its visibility.

To bind your applications with AnnotationTransactionAspect, youf must either build your application using AspectJ (see "AspectJ Development Guide"), or use loading-time binding. See "Load-time binding with AspectJ in the Spring Framework" for an introduction to loading-time binding with using AspectJ.