Note | |
---|---|
Most Spring Framework users choose declarative transaction management. This option has the least impact on application code, and hence is most consistent with the ideals of a non-invasive lightweight container. |
The Spring Framework's declarative transaction management is made possible with Spring aspect-oriented programming (AOP), although, as the transactional aspects code comes with the Spring Framework distribution and may be used in a boilerplate fashion, AOP concepts do not generally have to be understood to make effective use of this code.
The Spring Framework's declarative transaction management is similar
to EJB CMT in that you can specify transaction behavior (or lack of it)
down to individual method level. It is possible to make a
setRollbackOnly()
call within a transaction
context if necessary. The differences between the two types of transaction
management are:
Unlike EJB CMT, which is tied to JTA, the Spring Framework's declarative transaction management works in any environment. It can work with JTA transactions or local transactions using JDBC, JPA, Hibernate or JDO by simply adjusting the configuration files.
You can apply the Spring Framework declarative transaction management to any class, not merely special classes such as EJBs.
The Spring Framework offers declarative rollback rules, a feature with no EJB equivalent. Both programmatic and declarative support for rollback rules is provided.
The Spring Framework enables you to customize transactional
behavior, by using AOP. For example, you can insert custom behavior in
the case of transaction rollback. You can also add arbitrary advice,
along with the transactional advice. With EJB CMT, cannot influence
the container's transaction management except with
setRollbackOnly()
.
The Spring Framework does not support propagation of transaction contexts across remote calls, as do high-end application servers. If you need this feature, we recommend that you use EJB. However, consider carefully before using such a feature, because normally, one does not want transactions to span remote calls.
The concept of rollback rules is important: they enable you to
specify which exceptions (and throwables) should
cause automatic rollback. You specify this declaratively, in
configuration, not in Java code. So, although you can still call
setRollbackOnly()
on the
TransactionStatus
object to roll back the
current transaction back, most often you can specify a rule that
MyApplicationException
must always result
in rollback. The significant advantage to this option is that business
objects do not depend on the transaction infrastructure. For example, they
typically do not need to import Spring transaction APIs or other Spring
APIs.
Although EJB container default behavior automatically rolls back the
transaction on a system exception (usually a runtime
exception), EJB CMT does not roll back the transaction automatically on an
application exception (that is, a checked exception
other than java.rmi.RemoteException
). While
the Spring default behavior for declarative transaction management follows
EJB convention (roll back is automatic only on unchecked exceptions), it
is often useful to customize this behavior.
It is not sufficient to tell you simply to annotate your classes
with the @Transactional
annotation, add
the line (<tx:annotation-driven/>
) to your
configuration, and then expect you to understand how it all works. This
section explains the inner workings of the Spring Framework's
declarative transaction infrastructure in the event of
transaction-related issues.
The most important concepts to grasp with regard to the Spring
Framework's declarative transaction support are that this support is
enabled via AOP
proxies, and that the transactional advice is driven
by metadata (currently XML- or annotation-based).
The combination of AOP with transactional metadata yields an AOP proxy
that uses a TransactionInterceptor
in conjunction
with an appropriate PlatformTransactionManager
implementation to drive transactions around method
invocations.
Note | |
---|---|
Spring AOP is covered in Chapter 7, Aspect Oriented Programming with Spring. |
Conceptually, calling a method on a transactional proxy looks like this...
Consider the following interface, and its attendant
implementation. This example uses the rote Foo
and Bar
tropes so that you can concentrate on the
transaction usage without focusing on the domain model. For the purposes
of this example, the fact that the
DefaultFooService
class throws
UnsupportedOperationException
instances
in the body of each implemented method is good; it allows you to see
transactions created and then rolled back in response to the
UnsupportedOperationException
instance.
// the 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); }
// an implementation of the above interface package x.y.service; public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { throw new UnsupportedOperationException(); } public Foo getFoo(String fooName, String barName) { throw new UnsupportedOperationException(); } public void insertFoo(Foo foo) { throw new UnsupportedOperationException(); } public void updateFoo(Foo foo) { throw new UnsupportedOperationException(); } }
Assume that the first two methods of the
FooService
interface,
getFoo(String)
and getFoo(String, String),
must execute in the context of a transaction with read-only
semantics, and that the other methods,insertFoo(Foo)
and updateFoo(Foo),
must execute in the context of a
transaction with read-write semantics. The following configuration is
explained in detail 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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- ensure that the above transactional advice runs for any execution of an operation defined by the FooService interface --> <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 the 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> <!-- similarly, don't forget the PlatformTransactionManager --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- other <bean/> definitions here --> </beans>
Examine the preceding configuration. You want to make a service
object, the fooService
bean, transactional. The
transaction semantics to apply are encapsulated in the
<tx:advice/>
definition. The
<tx:advice/>
definition reads as
“... all methods on starting with
'get'
are to execute in the context of a read-only
transaction, and all other methods are to execute with the default
transaction semantics”. The
transaction-manager
attribute of the
<tx:advice/>
tag is set to the name of the
PlatformTransactionManager
bean that is
going to drive the transactions, in this case, the
txManager
bean.
Tip | |
---|---|
You can omit the |
The <aop:config/>
definition ensures that
the transactional advice defined by the txAdvice
bean
executes at the appropriate points in the program. First you define a
pointcut that matches the execution of any operation defined in the
FooService
interface
(fooServiceOperation
). Then you associate the
pointcut with the txAdvice
using an advisor. The
result indicates that at the execution of a
fooServiceOperation
, the advice defined by
txAdvice
will be run.
The expression defined within the
<aop:pointcut/>
element is an AspectJ pointcut
expression; see Chapter 7, Aspect Oriented Programming with Spring for more details on pointcut
expressions in Spring 2.0.
A common requirement is to make an entire service layer transactional. The best way to do this is simply to change the pointcut expression to match any operation in your service layer. For example:
<aop:config> <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> </aop:config>
Note | |
---|---|
In this example it is assumed that all your service
interfaces are defined in the |
Now that we've analyzed the configuration, you may be asking yourself, “Okay... but what does all this configuration actually do?”.
The above configuration will be used to create a transactional
proxy around the object that is created from the
fooService
bean definition. The
proxy will be configured with the transactional advice, so that when an
appropriate method is invoked on the proxy, a
transaction is started, suspended, marked as read-only, and so on,
depending on the transaction configuration associated with that method.
Consider the following program that test drives the above
configuration:
public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class); FooService fooService = (FooService) ctx.getBean("fooService"); fooService.insertFoo (new Foo()); } }
The output from running the preceding program will resemble the following. (The Log4J output and the stack trace from the UnsupportedOperationException thrown by the insertFoo(..) method of the DefaultFooService class have been truncated for clarity.)
<!-- the Spring container is starting up... --> [AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors <!-- the DefaultFooService is actually proxied --> [JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService] <!-- ... the insertFoo(..) method is now being invoked on the proxy --> [TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo <!-- the transactional advice kicks in here... --> [DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo] [DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction <!-- the insertFoo(..) method 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 the transaction is rolled back (by default, RuntimeException instances cause 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 elements removed for clarity --> at $Proxy0.insertFoo(Unknown Source) at Boot.main(Boot.java:11)
The previous section outlined the basics of how to specify transactional settings for classes, typically service layer classes, declaratively in your application. This section describes how you can control the rollback of transactions in a simple declarative fashion.
The recommended way to indicate to the Spring Framework's
transaction infrastructure that a transaction's work is to be rolled
back is to throw an Exception
from code
that is currently executing in the context of a transaction. The Spring
Framework's transaction infrastructure code will catch any unhandled
Exception
as it bubbles up the call
stack, and make a determination whether to mark the transaction for
rollback.
In its default configuration, the Spring Framework's transaction
infrastructure code only marks a transaction for
rollback in the case of runtime, unchecked exceptions; that is, when the
thrown exception is an instance or subclass of
RuntimeException
.
(Error
s will also - by default - result
in a rollback). Checked exceptions that are thrown from a transactional
method do not result in rollback in the default
configuration.
You can configure exactly which
Exception
types mark a transaction for
rollback, including checked exceptions. The following XML snippet
demonstrates how you configure rollback for a checked,
application-specific Exception
type.
<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>
You can also specify 'no rollback rules', if you do
not want a transaction rolled back when an
exception is thrown. The following example tells the Spring Framework's
transaction infrastructure to commit the attendant transaction even in
the face of an unhandled
InstrumentNotFoundException
.
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
When the Spring Framework's transaction infrastructure catches an
exception and is consults configured rollback rules to determine whether
to mark the transaction for rollback, the strongest
matching rule wins. So in the case of the following configuration, any
exception other than an
InstrumentNotFoundException
results in a
rollback of the attendant transaction.
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/> </tx:attributes> </tx:advice>
You can also indicate a required rollback programmatically. Although very simple, this process is quite invasive, and tightly couples your code to the Spring Framework's transaction infrastructure:
public void resolvePosition() { try { // some business logic... } catch (NoProductInStockException ex) { // trigger rollback programmatically TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
You are strongly encouraged to use the declarative approach to rollback if at all possible. Programmatic rollback is available should you absolutely need it, but its usage flies in the face of achieving a clean POJO-based architecture.
Consider the scenario where you have a number of service layer
objects, and you want to apply a totally different
transactional configuration to each of them. You do this by defining
distinct <aop:advisor/>
elements with differing
pointcut
and advice-ref
attribute
values.
As a point of comparison, first assume that all of your service
layer classes are defined in a root x.y.service
package. To make all beans that are instances of classes defined in that
package (or in subpackages) and that have names ending in
Service
have the default transactional configuration,
you would 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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.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 beans won't --> <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 a PlatformTransactionManager omitted... --> </beans>
The following example shows how to configure two distinct beans with totally different transactional settings.
<?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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.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' pointcut) --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- this bean will also be transactional, but with totally different transactional settings --> <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 a PlatformTransactionManager omitted... --> </beans>
This section summarizes the various transactional settings that
can be specified using the <tx:advice/>
tag.
The default <tx:advice/>
settings are:
Propagation setting is
REQUIRED.
Isolation level is DEFAULT.
Transaction is read/write.
Transaction timeout defaults to the default timeout of the underlying transaction system, or none if timeouts are not supported.
Any RuntimeException
triggers
rollback, and any checked Exception
does not.
You can change these default settings; the various attributes of
the <tx:method/>
tags that are nested within
<tx:advice/>
and
<tx:attributes/>
tags are summarized
below:
Table 10.1. <tx:method/>
settings
Attribute | Required? | Default | Description |
---|---|---|---|
name | Yes | Method name(s) with which the transaction
attributes are to be associated. The wildcard (*) character
can be used to associate the same transaction attribute
settings with a number of methods; for example,
| |
propagation | No | REQUIRED | Transaction propagation behavior. |
isolation | No | DEFAULT | Transaction isolation level. |
timeout | No | -1 | Transaction timeout value (in seconds). |
read-only | No | false | Is this transaction read-only? |
rollback-for | No |
| |
no-rollback-for | No |
|
In addition to the XML-based declarative approach to transaction configuration, you can use an annotation-based approach. Declaring transaction semantics directly in the Java source code puts the declarations much closer to the affected code. There is not much danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway.
The ease-of-use afforded by the use of the
@Transactional
annotation is best
illustrated with an example, which is explained in the text that
follows. Consider the following class definition:
// the service class that we want to make transactional @Transactional public class DefaultFooService implements FooService { Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
When the above POJO is defined as a bean in a Spring IoC container, the bean instance can be made transactional by adding merely one line of XML configuration:
<!-- 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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager"/> <!-- a PlatformTransactionManager is still required --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- other <bean/> definitions here --> </beans>
Tip | |
---|---|
You can omit the |
You can place the @Transactional
annotation before an interface definition, a method on an interface, a
class definition, or a public method on a class.
However, the mere presence of the
@Transactional
annotation is not enough
to activate the transactional behavior. The
@Transactional
annotation is simply
metadata that can be consumed by something that is
@Transactional
-aware and that can use the
metadata to configure the appropriate beans with transactional behavior.
In the preceding example, the
<tx:annotation-driven/>
element
switches on the transactional behavior.
Tip | |
---|---|
Spring recommends that you only annotate concrete classes (and
methods of concrete classes) with the
|
Note | |
---|---|
In proxy mode (which is the default), only external method calls
coming in through the proxy are intercepted. This means that
self-invocation, in effect, a method within the target object calling
another method of the target object, will not lead to an actual
transaction at runtime even if the invoked method is marked with
|
Consider the use of AspectJ mode (see mode attribute in table
below) if you expect self-invocations to be wrapped with transactions as
well. In
this case, there will not be a proxy in the first place; instead, the
target class will be weaved (that is, its byte code will be modified) in
order to turn @Transactional
into runtime
behavior on any kind of method.
Table 10.2. <tx:annotation-driven/>
settings
Attribute | Default | Description |
---|---|---|
transaction-manager | transactionManager | Name of transaction manager to use. Only required
if the name of the transaction manager is not
|
mode | proxy | The default mode "proxy" processes annotated beans to be proxied using Spring's AOP framework (following proxy semantics, as discussed above, applying to method calls coming in through the proxy only). The alternative mode "aspectj" instead weaves the affected classes with Spring's AspectJ transaction aspect, modifying the target class byte code to apply to any kind of method call. AspectJ weaving requires spring-aspects.jar in the classpath as well as load-time weaving (or compile-time weaving) enabled. (See Section 7.8.4.5, “Spring configuration” for details on how to set up load-time weaving.) |
proxy-target-class | false | Applies to proxy mode only. Controls what type of
transactional proxies are created for classes annotated with
the |
order | Ordered.LOWEST_PRECEDENCE | Defines the order of the transaction advice that
is applied to beans annotated with
|
Note | |
---|---|
The |
Note | |
---|---|
|
The most derived location takes precedence when evaluating the
transactional settings for a method. In
the case of the following example, the
DefaultFooService
class is annotated at the class
level with the settings for a read-only transaction, but the
@Transactional
annotation on the
updateFoo(Foo)
method in the same class takes
precedence over the transactional settings defined at the class
level.
@Transactional(readOnly = true) public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { // do something } // these settings have precedence for this method @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateFoo(Foo foo) { // do something } }
The @Transactional
annotation is
metadata that specifies that an interface, class, or method must have
transactional semantics; for example, “start a brand
new read-only transaction when this method is invoked, suspending any
existing transaction”. The default
@Transactional
settings are as
follows:
Propagation setting is
PROPAGATION_REQUIRED.
Isolation level is
ISOLATION_DEFAULT.
Transaction is read/write.
Transaction timeout defaults to the default timeout of the underlying transaction system, or to none if timeouts are not supported.
Any RuntimeException
triggers
rollback, and any checked Exception
does not.
These default settings can be changed; the various properties of
the @Transactional
annotation are
summarized in the following table:
Table 10.3. @Transactional
properties
Property | Type | Description |
---|---|---|
propagation | enum: Propagation | Optional propagation setting. |
isolation | enum: Isolation | Optional isolation level. |
readOnly | boolean | Read/write vs. read-only transaction |
timeout | int (in seconds granularity) | Transaction timeout. |
rollbackFor | Array of Class objects, which
must be derived from
Throwable. | Optional array of exception classes that must cause rollback. |
rollbackForClassname | Array of class names. Classes must be derived from
Throwable. | Optional array of names of exception classes that must cause rollback. |
noRollbackFor | Array of Class objects, which
must be derived from
Throwable. | Optional array of exception classes that must not cause rollback. |
noRollbackForClassname | Array of String class names,
which must be derived from
Throwable. | Optional array of names of exception classes that must not cause rollback. |
Currently you cannot have explicit control over the name of a
transaction, where 'name' means the transaction name that will be
shown in a transaction monitor, if applicable (for example, WebLogic's
transaction monitor), and in logging output. For declarative
transactions, the transaction name is always the fully-qualified class
name + "." + method
name of the transactionally-advised class. For example, if the
handlePayment(..)
method of the
BusinessService
class started a transaction,
the name of the transaction would be:
com.foo.BusinessService.handlePayment
.
This section describes some semantics of transaction propagation in Spring. Please note that this section is not an introduction to transaction propagation proper; rather it details some of the semantics regarding transaction propagation in Spring.
In Spring-managed transactions, be aware of the difference between physical and logical transactions, and how the propagation setting applies to this difference.
When the propagation setting is
PROPAGATION_REQUIRED
, a
logical transaction scope is created for each
method that to which the setting is applied. Each such logical
transaction scope can determine rollback-only status individually,
with an outer transaction scope being logically independent from the
inner transaction scope. Of course, in case of standard
PROPAGATION_REQUIRED
behavior, all these scopes
will be mapped to the same physical transaction. So a rollback-only
marker set in the inner transaction scope does affect the outer
transaction's chance to actually commit (as you would expect it
to).
However, in the case where an inner transaction scope sets the
rollback-only marker, the outer transaction has not decided on the
rollback itself, and so the rollback (silently triggered by the inner
transaction scope) is unexpected. A corresponding
UnexpectedRollbackException
is thrown at that
point. This is expected behavior so that the
caller of a transaction can never be misled to assume that a commit
was performed when it really was not. So if an inner transaction (of
which the outer caller is not aware) silently marks a transaction as
rollback-only, the outer caller still calls commit. The outer caller
needs to receive an UnexpectedRollbackException
to indicate clearly that a rollback was performed instead.
PROPAGATION_REQUIRES_NEW
, in contrast to
PROPAGATION_REQUIRED, uses a
completely independent transaction for each
affected transaction scope. In that case, the underlying physical
transactions are different and hence can commit or roll back
independently, with an outer transaction not affected by an inner
transaction's rollback status.
PROPAGATION_NESTED
uses a
single physical transaction with multiple
savepoints that it can roll back to. Such partial rollbacks allow an
inner transaction scope to trigger a rollback for its
scope, with the outer transaction being able to continue
the physical transaction despite some operations having been rolled
back. This setting is typically mapped onto JDBC savepoints, so will
only work with JDBC resource transactions. See Spring's
DataSourceTransactionManager
.
Suppose you want to execute both
transactional and some basic profiling advice. How
do you effect this in the context of
<tx:annotation-driven/>
?
When you invoke the updateFoo(Foo)
method, you want to see the following actions:
Configured profiling aspect starts up.
Transactional advice executes.
Method on the advised object executes.
Transaction commits.
Profiling aspect reports exact duration of the whole transactional method invocation.
Note | |
---|---|
This chapter is not concerned with explaining AOP in any great detail (except as it applies to transactions). See Chapter 7, Aspect Oriented Programming with Spring for detailed coverage of the following AOP configuration and AOP in general. |
Here is the code for a simple profiling aspect discussed above.
The
ordering of advice is controlled through the
Ordered
interface. For full details on
advice ordering, see Section 7.2.4.7, “Advice ordering”.
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 the around advice 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; } }
<?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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- this is the aspect --> <bean id="profiler" class="x.y.SimpleProfiler"> <!-- execute before the transactional advice (hence the lower order number) --> <property name="order" value="1"/> </bean> <tx:annotation-driven transaction-manager="txManager" order="200"/> <aop:config> <!-- this advice will execute around 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>
The result of the above configuration is a
fooService
bean that has profiling and transactional
aspects applied to it in the desired order. You
configure any number of additional aspects in similar fashion.
The following example effects the same setup as above, but uses the purely XML declarative 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 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- the profiling advice --> <bean id="profiler" class="x.y.SimpleProfiler"> <!-- execute before the transactional advice (hence the lower order number) --> <property name="order" value="1"/> </bean> <aop:config> <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/> <!-- will execute after the profiling advice (c.f. the order attribute) --> <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/> <!-- order value is higher than 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 a DataSource and a PlatformTransactionManager here --> </beans>
The result of the above configuration will be a
fooService
bean that has profiling and transactional
aspects applied to it in that order. If you want
the profiling advice to execute after the
transactional advice on the way in, and before the
transactional advice on the way out, then you simply swap the value of
the profiling aspect bean's order
property so that it
is higher than the transactional advice's order value.
You configure additional aspects in similar fashion.
It is also possible to use the Spring Framework's
@Transactional
support outside of a
Spring container by means of an AspectJ aspect. To do so, you first
annotate your classes (and optionally your classes' methods) with the
@Transactional
annotation, and then you
link (weave) your application with the
org.springframework.transaction.aspectj.AnnotationTransactionAspect
defined in the spring-aspects.jar
file. The aspect must
also be configured with a transaction manager. You can of course use the
Spring Framework's IoC container to take care of dependency-injecting
the aspect. The simplest way to configure the transaction management
aspect is to use the <tx:annotation-driven/>
element and specify the mode
attribute to
asepctj
as described in Section 10.5.6, “Using @Transactional”. Because we're focusing
here on applications running outside of a Spring container, we'll show
you how to do it programmatically.
Note | |
---|---|
Prior to continuing, you may want to read Section 10.5.6, “Using @Transactional” and Chapter 7, Aspect Oriented Programming with Spring respectively. |
// construct an appropriate transaction manager DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource()); // configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
Note | |
---|---|
When using this aspect, you must annotate the implementation class (and/or methods within that class), not the interface (if any) that the class implements. AspectJ follows Java's rule that annotations on interfaces are not inherited. |
The @Transactional
annotation on a
class specifies the default transaction semantics for the execution of
any method in the class.
The @Transactional
annotation on a
method within the class overrides the default transaction semantics
given by the class annotation (if present). Any method may be annotated,
regardless of visibility.
To weave your applications with the
AnnotationTransactionAspect
you must either build
your application with AspectJ (see the AspectJ
Development Guide) or use load-time weaving. See Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework” for a discussion of load-time weaving with
AspectJ.