spring 事务管理文档意译(3/5)

来源:互联网 发布:php高级书籍推荐 编辑:程序博客网 时间:2024/06/05 02:42

12.5 声明式事务管理

[Note]

多数的spring使用者选择使用声明式事务管理,这个方案对代码的影响最小,所以最能体现非侵入式轻量级容器的原则

spring声明式事务管理是通过spring 面向切面特性实现的。虽然事务的切面代码在spring的分发版中,而且可以以简单重复的方式使用,你要使用这个特性,不需要非常了解AOP的概念。

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:

spring声明式事务管理和EJB CMT的非常类似,因为如果你指定了单个方法的事务行为,事务管理机制可以在事务上下文中调用 setRollbackOnly() ,两种事务管理机制的区别主要是

  • 和EJB CMT不同, spring事务管理不绑定在JTA上,spring事务管理在任何环境下都可以使用。他可以和JTA或者JDBC,JPA,Hibernate,JDO这样数据技术配合使用,只需要简单修改配置文件就可以。
  • 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, you 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 anapplication 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.

12.5.1 Understanding the Spring Framework’s declarative transaction implementation

It is not sufficient to tell you simply to annotate your classes with the @Transactional annotation, add @EnableTransactionManagement 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 9, Aspect Oriented Programming with Spring.

Conceptually, calling a method on a transactional proxy looks like this…

Figure 12.1. 

tx

12.5.2 Example of declarative transaction implementation

Consider the following interface, and its attendant implementation. This example uses Foo and Bar classes as placeholders so that you can concentrate on the transaction usage without focusing on a particular domain model. For the purposes of this example, the fact that the DefaultFooService class throwsUnsupportedOperationException 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 transactionalpackage 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 interfacepackage 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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://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"/>    <!-- 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 thePlatformTransactionManager bean that is going to drive the transactions, in this case, the txManager bean.

[Tip]

You can omit the transaction-manager attribute in the transactional advice ( <tx:advice/>) if the bean name of the PlatformTransactionManagerthat you want to wire in has the name transactionManager. If the PlatformTransactionManager bean that you want to wire in has any other name, then you must use the transaction-manager attribute explicitly, as in the preceding example.

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 thetxAdvice 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 9, Aspect Oriented Programming with Spring for more details on pointcut expressions in Spring.

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 x.y.service package; see Chapter 9, Aspect Oriented Programming with Spring for more details.

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 DataSourceException 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)

12.5.3 Rolling back a declarative transaction

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. ( Errors 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.

12.5.4 Configuring different transactional semantics for different beans

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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://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 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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://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' 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>

12.5.5 <tx:advice/> settings

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 12.1. <tx:method/> settings

AttributeRequired?DefaultDescription

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, get*,handle*on*Event, and so forth.

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

 

Exception(s) that trigger rollback; comma-delimited. For example,com.foo.MyBusinessException,ServletException.

no-rollback-for

No

 

Exception(s) that do not trigger rollback; comma-delimited. For example,com.foo.MyBusinessException,ServletException.


12.5.6 Using @Transactional

In addition to the XML-based declarative approach to transaction configuration, you can use an annotation-based approach. Declaring transaction semantics directly in the Java source code puts the declarations much closer to the affected code. There is not much danger of undue coupling, because code that is meant to be used transactionally is almost always deployed that way anyway.

The 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@Transactionalpublic 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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.xsd">    <!-- this is the service object that we want to make transactional -->    <bean id="fooService" class="x.y.service.DefaultFooService"/>    <!-- enable the configuration of transactional behavior based on annotations -->    <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 transaction-manager attribute in the <tx:annotation-driven/> tag if the bean name of the PlatformTransactionManager that you want to wire in has the name transactionManager. If the PlatformTransactionManager bean that you want to dependency-inject has any other name, then you have to use the transaction-manager attribute explicitly, as in the preceding example.

[Note]

The @EnableTransactionManagement annotation provides equivalent support if you are using Java based configuration. Simply add the annotation to a@Configuration class. See the javadocs for full details.

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 some runtime infrastructure 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 @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies ( proxy-target-class="true") or the weaving-based aspect ( mode="aspectj"), then the transaction settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a transactional proxy, which would be decidedly bad.

[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 @Transactional.

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 12.2. Annotation driven transaction settings

XML AttributeAnnotation AttributeDefaultDescription

transaction-manager

N/A (SeeTransactionManagementConfigurerjavadocs)

transactionManager

Name of transaction manager to use. Only required if the name of the transaction manager is nottransactionManager, as in the example above.

mode

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. (Seethe section called “Spring configuration” for details on how to set up load-time weaving.)

proxy-target-class

proxyTargetClass

false

Applies to proxy mode only. Controls what type of transactional proxies are created for classes annotated with the @Transactional annotation. If the proxy-target-class attribute is set to true, then class-based proxies are created. Ifproxy-target-class is false or if the attribute is omitted, then standard JDK interface-based proxies are created. (See Section 9.6, “Proxying mechanisms”for a detailed examination of the different proxy types.)

order

order

Ordered.LOWEST_PRECEDENCE

Defines the order of the transaction advice that is applied to beans annotated with @Transactional. (For more information about the rules related to ordering of AOP advice, see the section called “Advice ordering”.) No specified ordering means that the AOP subsystem determines the order of the advice.


[Note]

The proxy-target-class attribute controls what type of transactional proxies are created for classes annotated with the @Transactional annotation. Ifproxy-target-class is set to true, class-based proxies are created. If proxy-target-class is false or if the attribute is omitted, standard JDK interface-based proxies are created. (See Section 9.6, “Proxying mechanisms” for a discussion of the different proxy types.)

[Note]

@EnableTransactionManagement and <tx:annotation-driven/> only looks for @Transactional on beans in the same application context they are defined in. This means that, if you put annotation driven configuration in a WebApplicationContext for a DispatcherServlet, it only checks for@Transactional beans in your controllers, and not your services. See Section 17.2, “The DispatcherServlet” for more information.

The most derived location takes precedence when evaluating the transactional settings for a method. In the case of the following example, the DefaultFooServiceclass 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    }}

@Transactional settings

The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics; for example, "start a brand new read-only transaction when this method is invoked, suspending any existing transaction". The default @Transactional settings are as follows:

  • 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 12.3. @

PropertyTypeDescription

value

String

Optional qualifier specifying the transaction manager to be used.

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 fromThrowable.

Optional array of exception classes that must cause rollback.

rollbackForClassName

Array of class names. Classes must be derived fromThrowable.

Optional array of names of exception classes that must cause rollback.

noRollbackFor

Array of Class objects, which must be derived fromThrowable.

Optional array of exception classes that must not cause rollback.

noRollbackForClassName

Array of String class names, which must be derived fromThrowable.

Optional array of names of exception classes that must notcause 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.

Multiple Transaction Managers with @Transactional

Most Spring applications only need a single transaction manager, but there may be situations where you want multiple independent transaction managers in a single application. The value attribute of the @Transactional annotation can be used to optionally specify the identity of the PlatformTransactionManager to be used. This can either be the bean name or the qualifier value of the transaction manager bean. For example, using the qualifier notation, the following Java code

public class TransactionalService {    @Transactional("order")    public void setSomething(String name) { ... }    @Transactional("account")    public void doSomething() { ... }}

could be combined with the following transaction manager bean declarations in the application context.

<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>

In this case, the two methods on TransactionalService will run under separate transaction managers, differentiated by the "order" and "account" qualifiers. The default <tx:annotation-driven> target bean name transactionManager will still be used if no specifically qualified PlatformTransactionManager bean is found.

Custom shortcut annotations

If you find you are repeatedly using the same attributes with @Transactional on many different methods, then Spring’s meta-annotation support allows you to define custom shortcut annotations for your specific use cases. For example, defining the following annotations

@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("order")public @interface OrderTx {}@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Transactional("account")public @interface AccountTx {}

allows us to write the example from the previous section as

public class TransactionalService {    @OrderTx    public void setSomething(String name) { ... }    @AccountTx    public void doSomething() { ... }}

Here we have used the syntax to define the transaction manager qualifier, but could also have included propagation behavior, rollback rules, timeouts etc.

12.5.7 Transaction propagation

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.

Required

Figure 12.2. 

tx prop required

PROPAGATION_REQUIRED

When the propagation setting is PROPAGATION_REQUIRED, a logical transaction scope is created for each method upon 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.

RequiresNew

Figure 12.3. 

tx prop requires new

PROPAGATION_REQUIRES_NEW

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.

Nested

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.

12.5.8 Advising transactional operations

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 9, 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 the section called “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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.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.xsd        http://www.springframework.org/schema/tx        http://www.springframework.org/schema/tx/spring-tx.xsd        http://www.springframework.org/schema/aop        http://www.springframework.org/schema/aop/spring-aop.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’sorder property so that it is higher than the transactional advice’s order value.

You configure additional aspects in similar fashion.

12.5.9 Using @Transactional with AspectJ

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 theorg.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 aspectj as described in Section 12.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 12.5.6, “Using @Transactional” and Chapter 9, Aspect Oriented Programming with Spring respectively.

// construct an appropriate transaction managerDataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methodsAnnotationTransactionAspect.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 9.8.4, “Load-time weaving with AspectJ in the Spring Framework” for a discussion of load-time weaving with AspectJ.

0 0
原创粉丝点击