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.
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:
// 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);
}
// 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:
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) {
// ...
}
}
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
).
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>
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:
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());
}
}
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 .
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:
// 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);
}
// 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:
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) {
// ...
}
}
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 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, 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 |
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:
public void resolvePosition() {
try {
// some business logic...
} catch ( NoProductInStockException ex) {
// start the rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
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 checkedException
- 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:
Attribute | Required? | Default | Description |
---|---|---|---|
|
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,
|
|
|
No |
|
Logic of operation when propagating transactions. |
|
No |
|
Transaction isolation level. Applies only to |
|
No |
-1 |
Transaction timeout (seconds). Applies only to |
|
No |
false |
Transaction for " read-write vs. read-only transaction. Applies only to |
|
None |
List of |
|
|
No |
List of |
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.
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:
// 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) {
// ...
}
}
// 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>
- A string that makes the bean instance transactional.
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:
// 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) {
// ...
}
}
// 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.
@Transactional
annotationIf 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 @EnableTransactionManagement
annotation 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.
@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.
@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.
XML Attribute | Annotation Attribute | Default | Description |
---|---|---|---|
|
N/A (see javadoc at |
|
The name of the transaction manager to use. Required only if the transaction manager name is not |
|
|
|
The default mode ( |
|
|
|
Applies only in |
|
|
|
Defines the order of transactional Advice that is applied to beans marked with the
|
@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.
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.
@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) {
// ...
}
}
@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
orError
cause a rollback, but any checkedException
does not.
You can change these default settings. The following table shows the various properties of the @Transactional
annotation:
Property | Type | Description |
---|---|---|
|
|
An optional qualifier that specifies the transaction manager to use. |
|
|
An alias for |
|
An array of |
Labels can be computed by transaction managers to associate a transaction-specific implementing the logic for working with the actual transaction. |
|
Additional distribution parameter. |
|
|
|
Additional isolation level. Applies only to |
|
|
Optional transaction timeout. Applies only to |
|
|
Alternative for setting |
|
|
Transaction in read-write mode versus transaction in read-only mode. Applies only to
|
|
An array of |
An optional array of exception types that should cause a rollback. |
|
An array of exception name patterns. |
An optional array of exception name patterns that should trigger a rollback. |
|
Array of |
An optional array of exception types that should not cause a rollback. |
|
An array of exception name patterns. |
An optional array of exception name patterns that are not should cause a rollback. |
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:
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.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:
@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 previous annotations allow us to write the example from the previous section as follows:
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 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).
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.
The following code shows the simple profiling aspect discussed earlier:
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;
}
}
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.
@Transactional
" annotation and "AOP" respectively.
The following example shows how to create a transaction manager and configure AnnotationTransactionAspect
to use it:
// 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);
// 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
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.
GO TO FULL VERSION