Chapter 9. 事务管理

9.1. 简介

Spring引人注目的一个因素是事务支持。Spring提供了一致的事务管理抽象,这带来了以下好处:

  • 为不同的事务API提供了一致的编程模型,如JTA、JDBC、Hibernate、JPA和JDO

  • 支持声明式事务管理

  • 提供比大多数事务API(诸如JTA)更简单的,更易于使用的编程式事务管理API

  • 整合Spring的各种数据访问抽象

这章被分成几个小节,下面列出了每节的描述和链接,你可以直接跳到你所感兴趣的部分。

  • 第一节,动机,描述为何愿意使用Spring的事务抽象,而不是EJB CMT或者一个私有的API,比如Hibernate的事务处理。

  • 第二节,关键抽象,列举了Spring事务支持的核心类,以及如何从多种不同的数据源去配置并获得一个DataSource实例。

  • 第三节,声明式事务管理,讲述了Spring如何支持声明式事务管理。

  • 第四节,编程式事务管理,介绍了Spring如何支持编程式(即硬编码)事务管理。

本章末尾讨论了一些关于事务管理的最佳实践(比如,如何在编程式和声明式事务管理之间做选择)。

9.2. 动机

传统上,J2EE开发者有两个事务管理的选择: 全局本地事务。全局事务由应用服务器管理,使用JTA。局部事务是和资源相关的,比如一个和JDBC连接关联的事务。这个选择有深刻的含义。例如,全局事务可以用于多个事务性的资源(典型例子是关系数据库和消息队列)。使用局部事务,应用服务器不需要参与事务管理,并且不能帮助确保跨越多个资源(需要指出的是多数应用使用单一事务性的资源)的事务的正确性。

全局事务. 全局事务有一个重大的缺陷,代码需要使用JTA:一个笨重的API(部分是因为它的异常模型)。此外,JTA的UserTransaction通常需要从JNDI获得,这意味着我们为了JTA,需要 同时 使用JNDI JTA。显然全部使用全局事务限制了应用代码的重用性,因为JTA通常只在应用服务器的环境中才能使用。以前,使用全局事务的首选方式是通过EJB的 CMT容器管理事务):CMT是声明式事务管理的一种形式(区别于编程式事务管理)。EJB的CMT不需要任何和事务相关的JNDI查找,虽然使用EJB本身肯定需要使用JNDI。它消除了大多数(不是全部)硬编码的方式去控制事务。重大的缺陷是CMT绑定在JTA和应用服务器环境上,并且只有我们选择使用EJB实现业务逻辑,或者至少处于一个事务化EJB的外观(Facade)后才能使用它。EJB有如此多的诟病,尤其是存在其它声明式事务管理时,EJB不是一个吸引人的建议。

本地事务. 本地事务容易使用,但也有明显的缺点:它们不能用于多个事务性资源。例如,使用JDBC连接事务管理的代码不能用于全局的JTA事务中。另一个缺点是局部事务趋向于入侵式的编程模型。

Spring解决了这些问题。它使应用开发者能够使用在任何环境下使用一致的编程模型。你可以只写一次你的代码,这在不同环境下的不同事务管理策略中很有益处。Spring同时提供声明式和编程式事务管理。声明事务管理是多数使用者的首选,在多数情况下是被推荐使用的。

使用编程式事务管理,开发者直接使用Spring事务抽象,这个抽象可以使用在任何底层事务基础之上。使用首选的声明式模型,开发者通常书写很少的或没有与事务相关的代码,因此不依赖Spring或任何其他事务API。

9.3. 关键抽象

Spring事务抽象的关键是事务策略的概念。这个概念由org.springframework.transaction.PlatformTransactionManager接口定义如下:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这首先是一个SPI接口,虽然它也可以在编码中使用。注意按照Spring的哲学,PlatformTransactionManager是一个接口。因而如果需要它可以很容易地被模拟和桩化。它也没有和一个查找策略如JNDI捆绑在一起:PlatformTransactionManager的实现定义和其他Spring IoC容器中的对象一样。这个好处使得即使使用JTA,也是一个很有价值的抽象:事务代码可以比直接使用JTA更加容易测试。

继续Spring哲学,可由任何PlatformTransactionManager的接口方法抛出的TransactionException是unchecked exception(继承自java.lang.RuntimeException)的。底层的事务失败几乎总是致命的。很少情况下应用程序代码可以从它们中恢复,不过应用开发者依然可以捕获并处理TransactionException,他们可以自由决定怎么干。

getTransaction()方法根据一个类型为TransactionDefinition的参数返回一个TransactionStatus对象。返回的TransactionStatus对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务。如同J2EE事务环境,一个TransactionStatus也是和执行线程绑定的)。

TransactionDefinition接口指定:

  • 事务隔离:当前事务和其它事务的隔离的程度。例如,这个事务能否看到其他事务未提交的写数据?

  • 事务传播:通常在一个事务中执行的所有代码都会在这个事务中运行。但是,如果一个事务上下文已经存在,有几个选项可以指定一个事务性方法的执行行为:例如,简单地在现有的事务中运行(大多数情况);或者挂起现有事务,创建一个新的事务。Spring提供EJB CMT中常见的事务传播选项。

  • 事务超时: 事务在超时前能运行多久(自动被底层的事务基础设施回滚)。

  • 只读状态: 只读事务不修改任何数据。只读事务在某些情况下(例如当使用Hibernate时),是一种非常有用的优化。

这些设置反映了标准概念。如果需要,请查阅讨论事务隔离层次和其他核心事务概念的资源:理解这些概念在使用Spring和其他事务管理解决方案时是非常关键的。

TransactionStatus 接口为处理事务的代码提供一个简单的控制事务执行和查询事务状态的方法。这个概念应该是熟悉的,因为它们在所有的事务API中是相同的:

public interface TransactionStatus {

    boolean isNewTransaction();

    void setRollbackOnly();

    boolean isRollbackOnly();
}

使用Spring时,无论你选择编程式还是声明式的事务管理,定义一个正确的PlatformTransactionManager 实现都是至关重要的。按照Spring的风格,这种重要定义都是通过IoC实现的。

一般来说,选择PlatformTransactionManager实现时需要知道当前的工作环境,如JDBC、JTA、Hibernate等。下面的例子来自Spring示例应用——jPetStore——中的dataAccessContext-local.xml文件,其中展示了一个局部PlatformTransactionManager实现是怎么定义的(仅限于纯粹JDBC环境)

我们必须先定义一个JDBC DataSource,然后使用Spring的DataSourceTransactionManager,并传入指向DataSource的引用。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
</bean>

PlatformTransactionManager bean的定义如下:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

如果我们使用JTA,就像示例中dataAccessContext-jta.xml文件所示,我们将从JNDI中获取一个容器管理的DataSource,以及一个JtaTransactionManager实现。JtaTransactionManager不需要知道DataSource和其他特定的资源,因为它将使用容器提供的全局事务管理。

<?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:jndi="http://www.springframework.org/schema/jndi"
xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd">

  <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

  <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

  <!-- other <bean/> definitions here -->

</beans>
[Note]Note

上面'dataSource'的bean定义使用了'jee'名称空间下的'jndi-lookup'标签。想了解更多的配置信息, 请看 Appendix A, XML Schema-based configuration,关于<jee/>标签的信息,可参考Section A.2.3, “The jee schema”节。

我们可以很容易地使用Hibernate局部事务,就像下面的Spring的PetClinic示例应用中的例子一样)。这种情况下,我们需要定义一个Hibernate的LocalSessionFactoryBean,应用程序从中获取到Hibernate Session 实例。

DataSource的bean定义同上例类似,这里不再展示。(不过,如果是一个容器提供的DataSource,它将由容器自身,而不是Spring,来管理事务)。

这种情况中'txManager' bean的类型为HibernateTransactionManager。同样地,DataSourceTransactionManager需要一个指向DataSource的引用,而HibernateTransactionManager需要一个指向SessionFactory的引用。

<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mappingResources">
    <list>
      <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
    </list>
  </property>
  <property name="hibernateProperties">
    <value>
	  hibernate.dialect=${hibernate.dialect}
	</value>
  </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

我们可以简单地使用JtaTransactionManager来处理Hibernate事务和JTA事务,就像我们处理JDBC,或者任何其它的资源策略一样。

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

注意任何资源的JTA配置都是这样的,因为它们都是全局事务,可以支持任何事务性资源。

在所有这些情况下,应用程序代码根本不需要做任何改动。我们仅仅通过改变配置就可以改变事务管理方式,即使这些更改是在局部事务和全局事务间切换。

9.4. 使用资源同步的事务

现在应该比较清楚的是:不同的事务管理器是如何创建的,以及它们如何被连接到相应的需要被同步到事务的资源上(例如,DataSourceTransactionManager对应到JDBC DataSourceHibernateTransactionManager对应到Hibernate的SessionFactory等)。可是,剩下的问题是,直接或间接地使用一种持久化API(JDBC,Hibernate,JDO等)的应用代码,如何确保通过相关的PlatformTransactionManager来恰当地获取并操作资源,来满足事务同步,这些操作包括:创建/复用/清理 和 触发(可能没有)。

9.4.1. 高层次方案

首选的方法是使用Spring的高层持久化集成APIs。这种方式不会替换原始的APIs,而是在内部封装了资源创建、复用、清理、事务同步以及异常映射等功能,这样用户的数据访问代码就不必关心这些,而集中精力于自己的持久化逻辑。通常,对所有持久化API都采用这种 模板 方法,例如JdbcTemplateHibernateTemplateJdoTemplate等。这些集成功能类在这份参考文档后面的章节中详细叙述。

9.4.2. 低层次方案

在较低层次上,有以下这些类:DataSourceUtils (针对JDBC),SessionFactoryUtils (针对Hibernate),PersistenceManagerFactoryUtils (针对JDO),等等。当对应用代码来说,直接同原始持久化API特有的资源类型打交道是更好的选择时,这些类确保应用代码获取到正确的Spring受管bean,事务被正确同步,处理过程中的异常被映射到一致的API。

例如,在JDBC环境下,你不再使用传统的调用DataSourcegetConnection()方法的方式,而是使用Spring的org.springframework.jdbc.datasource.DataSourceUtils,像这样:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果已有一个事务及与之关联的connection存在,该实例将被返回。否则,该方法调用将触发起一个新的connection的创建动作,该connection(可选地)被同步到任何现有的事务,并可以在同一事务范围内被后续的调用复用。正如上面提到的,这个过程有一个额外的好处,就是任何SQLException将被包装为Spring的CannotGetJdbcConnectionException,该类是Spring的unchecked的DataAccessExceptions层次体系中的一员。这将给你比从SQLException中简单所得更多的信息,而且保证了跨数据库——甚至其他持久化技术——的移植性。

应该指出的是,这些类同样可以在没有Spring事务管理的环境中工作良好(事务同步能力是可选的),所以无论你是否使用Spring的事务管理,你都可以使用这些类。

当然,一旦你用过Spring的JDBC支持或Hibernate支持,你一般就不再会选择DataSourceUtils或是别的辅助类了,因为你会更乐意与Spring抽象一起工作,而不是直接使用相关的API。例如,如果你使用Spring的JdbcTemplatejdbc.object包来简化使用JDBC,Spring会在幕后替你正确地获取连接,而你不需要写任何特殊代码。

9.4.3.  TransactionAwareDataSourceProxy

工作在最底层的是TransactionAwareDataSourceProxy类。这是一个对目标DataSource的代理,它包装了目标DataSource,提供对Spring管理事务的可知性。在这点上,它类似于一个J2EE服务器提供的事务性JNDI DataSource

该类应该永远不需要被应用代码使用,除非现有代码存在需要直接传递一个标准的JDBC的DataSource的情况。这时可以通过参与Spring管理事务让这些代码仍然有用。书写新的代码时,首选的方法是采用上面提到的Spring高层抽象。

9.5. 声明式事务管理

大多数Spring用户选择声明式事务管理。这是对应用代码影响最小的选择,因此也最符合非侵入式轻量级容器的理念。

Spring的声明式事务管理是通过Spring AOP实现的,因为事务方面的代码与Spring绑定并以一种样板式风格使用,不过尽管如此,你一般并不需要理解AOP概念就可以有效地使用Spirng的声明式事务管理。

从考虑EJB CMT和Spring声明式事务管理的相似以及不同之处出发是很有益的。它们的基本方法是相似的:都可以指定事务管理到单独的方法;如果需要可以在事务上下文调用setRollbackOnly()方法。不同之处在于:

  • 不像EJB CMT绑定在JTA上,Spring声明式事务管理可以在任何环境下使用。只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作。

  • Spring的声明式事务管理可以被应用到任何类(以及那个类的实例)上,不仅仅是像EJB那样的特殊类。

  • Spring提供了声明式的回滚规则:EJB没有对应的特性,我们将在下面讨论。回滚可以声明式的控制,不仅仅是编程式的。

  • Spring允许你通过AOP定制事务行为。例如,如果需要,你可以在事务回滚中插入定制的行为。你也可以增加任意的通知,就象事务通知一样。使用EJB CMT,除了使用setRollbackOnly(),你没有办法能够影响容器的事务管理。

  • Spring不提供高端应用服务器提供的跨越远程调用的事务上下文传播。如果你需要这些特性,我们推荐你使用EJB。然而,不要轻易使用这些特性。通常我们并不希望事务跨越远程调用。

回滚规则的概念比较重要:它使我们能够指定什么样的异常(和throwables)将导致自动回滚。我们在配置文件中声明式地指定,无须在Java代码中。同时,我们仍旧可以通过调用TransactionStatussetRollbackOnly() 方法编程式地回滚当前事务。通常,我们定义一条规则,声明MyApplicationException应该总是导致事务回滚。这种方式带来了显著的好处,它使你的业务对象不必依赖于事务设施。典型的例子是你不必在代码中导入Spring API,事务等。

对EJB来说,默认的行为是EJB容器在遇到系统异常(通常指运行时异常)时自动回滚当前事务。EJB CMT遇到应用异常(例如,除了java.rmi.RemoteException外别的checked exception)时并不会自动回滚。默认式Spring处理声明式事务管理的规则遵守EJB习惯(只在遇到unchecked exceptions时自动回滚),但通常定制这条规则会更有用。

9.5.1. 理解Spring的声明式事务管理实现

本节的目的是消除与使用声明式事务管理有关的神秘性。简单点儿总是好的,这份参考文档只是告诉你给你的类加上@Transactional注解,在配置文件中添加('<tx:annotation-driven/>')行,然后期望你理解整个过程是怎么工作的。此节讲述Spring的声明式事务管理内部的工作机制,以帮助你在面对事务相关的问题时不至于误入迷途,回朔到上游平静的水域。

[Tip]Tip

阅读Spring源码是理解清楚Spring事务支持的一个好方法。Spring的Javadoc提供的信息丰富而完整。我们建议你在开发自己的Spring应用时把日志级别设为'DEBUG'级,这样你能更清楚地看到幕后发生的事。

在理解Spring的声明式事务管理方面最重要的概念是:Spring的事务管理是通过AOP代理实现的。其中的事务通知由元数据(目前基于XML或注解)驱动。代理对象与事务元数据结合产生了一个AOP代理,它使用一个PlatformTransactionManager实现品配合TransactionInterceptor,在方法调用前后实施事务。

[Note]Note

尽管使用Spring声明式事务管理不需要AOP(尤其是Spring AOP)的知识,但了解这些是很有帮助的。你可以在Chapter 6, 使用Spring进行面向切面编程(AOP)章找到关于Spring AOP的全部内容。

概念上来说,在事务代理上调用方法的工作过程看起来像这样:

9.5.2. 第一个例子

请看下面的接口和它的实现。这个例子的意图是介绍概念,使用 Foo Bar 这样的名字只是为了让你关注于事务的用法,而不是领域模型。

				<!-- 我们想做成事务性的服务/门面接口 -->
            

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();
    }
}

(对该例的目的来说,上例中实现类(DefaultFooService)的每个方法在其方法体中抛出UnsupportedOperationException的做法是恰当的,我们可以看到,事务被创建出来,响应UnsupportedOperationException的抛出,然后回滚。)

我们假定,FooService的前两个方法(getFoo(String)getFoo(String, String))必须执行在只读事务上下文中,其余方法(insertFoo(Foo)updateFoo(Foo))必须执行在读写事务上下文中。

使用XML方式元数据的声明式配置的话,你得这么写(不要想着一次全部理解,所有内容会在后面的章节详细讨论):

				<!-- 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 (i.e. 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="fooServiceMethods" expression="execution(* x.y.service.FooService.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
  </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 (particular) PlatformTransactionManager -->
  
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
  </bean>

  <!-- other <bean/> definitions here -->
  

</beans>

我们来分析一下上面的配置。我们要把一个服务对象('fooService' bean)做成事务性的。我们想施加的事务语义封装在<tx:advice/>定义中。<tx:advice/> 把所有以'get'开头的方法看做执行在只读事务上下文中,其余的方法执行在默认语义的事务上下文中 ”。其中的'transaction-manager'属性被设置为一个指向PlatformTransactionManager bean的名字(这里指'txManager'),该bean将实际上实施事务管理。

[Tip]Tip

事实上,如果PlatformTransactionManager bean的名字是'transactionManager'的话,你的事务通知(<tx:advice/>)中的'transaction-manager'属性可以忽略。否则你则需要像上例那样明确指定。

配置中最后一段是<aop:config/>的定义,它确保由'txAdvice' bean定义的事务通知在应用中合适的点被执行。首先我们定义了 一个切面,它匹配FooService接口定义的所有操作,我们把该切面叫做'fooServiceOperation'。然后我们用一个通知器(advisor)把这个切面与'txAdvice'绑定在一起,表示当'fooServiceOperation'执行时,'txAdvice'定义的通知逻辑将被执行。

<aop:pointcut/>元素定义是AspectJ的切面表示法,可参考Spring 2.0 Chapter 6, 使用Spring进行面向切面编程(AOP)章获得更详细的内容。

一个普遍性的需求是让整个服务层成为事务性的。满足该需求的最好方式是让切面表达式匹配服务层的所有操作方法。例如:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
  </aop:config>

(这个例子中假定你所有的服务接口定义在'x.y.service'包中。你同样可以参考Chapter 6, 使用Spring进行面向切面编程(AOP)章获得更详细内容。)

现在,既然我们已经分析了整个配置,你可能会问了,“ 好吧,但是所有这些配置了什么? ”。

上面的配置将为由'fooService'定义的bean创建一个代理对象,这个代理对象被装配了事务通知,所以当它的相应方法被调用时,一个事务将被启动、挂起、被标记为只读,或者其它(根据该方法所配置的事务语义)。

我们来看看下面的例子,测试一下上面的配置。

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());
    }
}

运行上面程序的输出结果看起来像这样(注意为了看着清楚,Log4j的消息和异常堆栈信息被省略了)。

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

9.5.3. 为不同的bean应用不同的事务语义

现在我们考虑一下这样的场景,你有许多服务对象,而且想为不同组的对象设置 完全不同 的事务语义。在Spring中,你可以通过定义各自特定的 <aop:advisor/> 元素,每个advisor采用不同的 'pointcut''advice-ref' 属性,来达到目的。

借助于一个例子,我们假定你所有的服务层类定义在以 'x.y.service'为根的包内,为了让service包(或子包)下所有名字以 'Service'结尾的类的对象(或者,更好的做法是,接口的实现类的对象)拥有默认的事务语义,你可以配置如下:

<?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="serviceOperationWithDefaultTxSemantics"
                    expression="execution(* x.y.service..*Service.*(..))"/>

    <aop:advisor pointcut-ref="serviceOperationWithDefaultTxSemantics"
                    advice-ref="txAdvice"/>

</aop:config>

<!-- these two beans will have the transactional advice applied to them -->

<bean id="fooService" class="org.xyz.service.DefaultFooService"/>
<bean id="barService" class="org.xyz.service.extras.SimpleBarService"/>

<!-- ...and these two beans won't -->

<bean id="fooService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->

<bean id="barService" class="org.xyz.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>

9.5.4. 使用@Transactional

[Note]Note

注意:@Transactional注解及其支持类仅适用于Java5(Tiger)。

除了基于XML文件的声明式事务配置外,你也可以采用注解式的事务配置方法——通过@Transactional 注解。

直接在Java源代码中声明事务语义的做法让事务声明和将受其影响的代码距离更近了,而且一般来说不会有不恰当的耦合的风险,因为,典型情况下,被部署为事务性的代码几乎总是运行在事务环境中。

下面的例子很好地演示了 @Transactional 的易用性,随后解释其中的细节。先看看其中的接口定义:

				<!-- the service (facade) interface that we want to make transactional -->
            
@Transactional
public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

在Spring配置文件中,上面 FooService 的实现类的bean可以仅仅通过 行xml配置为事务性的。如下:

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

  <!-- a PlatformTransactionManager is still required -->
  
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- sourced from somewhere else -->
    
    <property name="dataSource" ref="dataSource"/>
  </bean>

  <!-- other <bean/> definitions here -->
  

</beans>

@Transactional 可以被应用于接口定义和接口方法、类定义和类方法上。

[Note]Note

仅仅 @Transactional 的出现不足于开启事务行为,它仅仅是一种元数据,能够被可识别该注解并应用事务行为的代码所使用。

上面的例子中,其实正是 <tx:annotation-driven/>元素的出现 开启 了事务行为。

为了符合Spring的核心原则之一,即“按直觉做事”,Spring中对@Transactional 的处理方式考虑了继承性,这是有意义的。如果你在类层次上给一个接口加了 @Transactional 注解,那么所有实现该接口的类将继承施加在接口上的事务设置。这与注解本来的含义截然不同,通常加在接口和方法上的注解 从不 会被继承。使用Spring,你可以通过指定自己的 @Transactional来覆盖从接口或超类自动继承的事务设置。基本上,确定一个方法的事务语义时最优先考虑继承树上最末端的类。在下面的例子中,FooService 接口在类层次被注解为默认事务设置,但其实现类 DefaultFooService 的方法 updateFoo(Foo) 上的 @Transactional 却有更高的优先级,覆盖了从接口继承来的默认设置。

@Transactional(readOnly = true)
public interface FooService {

    Foo getFoo(String fooName);

    void updateFoo(Foo foo);
}
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
        
    }

    // these settings have precedence
    
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
        
    }
}
[Note]Note
注意,这种“纯粹直觉式”继承 @Transactional适用于Spring AOP驱动的事务管理环境。如果你采用别的事务管理策略,例如AspectJ驱动事务,则关于Java注解的一般规则仍然起效(即:没有继承)。

9.5.4.1.  @Transactional 有关的设置

在最简单的形式下,@Transactional指定一个接口、类、方法必须是事务性的,其默认事务语义为:read/write,PROPAGATION_REQUIREDISOLATION_DEFAULTTIMEOUT_DEFAULT,而且仅当遇到RuntimeException时回滚,而不是Exception

改变事务设置的其他可选属性

Table 9.1.  Transactional 注解的属性

属性类型描述
传播性枚举型:Propagation可选的传播性设置 (默认值:PROPAGATION_REQUIRED
隔离性枚举型:Isolation可选的隔离性级别(默认值:ISOLATION_DEFAULT
只读性布尔型读写型事务 vs. 只读型事务(默认值:false,即只读型事务)
回滚异常类(rollbackFor)一组 Class 类的实例,必须是Throwable 的子类 一组异常类,遇到时 确保 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。
回滚异常类名(rollbackForClassname)一组 Class 类的名字,必须是Throwable的子类一组异常类名,遇到时 确保 进行回滚
不回滚异常类(noRollbackFor)一组 Class 类的实例,必须是Throwable 的子类 一组异常类,遇到时确保 回滚。
不回滚异常类名(noRollbackForClassname)一组 Class 类的名字,必须是Throwable 的子类 一组异常类,遇到时确保 回滚

我们推荐你参考 @Transactional 注解的javadoc,其中详细列举了上述各项属性及其可选值。

9.5.5. 插入事务操作

考虑这样的情况,你有一个类的实例,而且希望 同时插入事务性通知(advice)和一些简单的剖析(profiling)通知。那么,在<tx:annotation-driven/>环境中该怎么做?

我们调用 updateFoo(Foo) 方法时希望这样: a)、剖析(profiling)方面(aspect)的代码启动,然后 b)、进入事务通知(根据配置创建一个新事务或加入一个已经存在的事务),然后 c)、原始对象的方法执行,然后 d)、事务提交(我们假定这里一切正常),最后 e)、剖析方面(aspect)报告整个执行过程花了多少时间。

[Note]Note

这章不是专门讲述AOP的(除了应用于事务方面的之外)。请参考Chapter 6, 使用Spring进行面向切面编程(AOP) 章以获得对各种AOP配置及其一般概念的详细叙述。

这里有一份简单的(还不怎么成熟)剖析方面(profiling aspect)的代码。(注意,通知的顺序由 Ordered接口控制。要想了解更多细节,请参考Section 6.2.4.7, “通知(Advice)顺序”节。)

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;

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

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

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

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

上面配置的结果是创建了一个 'fooService' bean,剖析方面和事务方面被 依照顺序 施加其上。如果我们希望剖析通知在目标方法执行之前 后于 事务通知执行,而且在目标方法执行之后 先于 事务通知,我们可以简单地交换两个通知bean的order值。

如果配置中包含更多的方面,它们将以同样的方式受到影响。

9.5.6. 结合AspectJ使用@Transactional

通过 spring-aspects.jar 提供的AspectJ方面,你也可以在Spring容器之外使用Spring的 @Transactional 功能。要使用这项功能首先你得给相应的类型和方法加上 @Transactional注解,然后把 spring-aspects.jar 中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect方面连接进(织入)你的应用。同样,该方面必须配置一个事务管理器;你当然可以通过Spring注入,但因为我们这里关注于在Spring容器之外运行应用,我们将向你展示如何通过手动书写代码来完成。

[Note]Note

在我们继续之前,你可能需要好好读一下前面的Section 9.5.4, “使用@Transactional ”Chapter 6, 使用Spring进行面向切面编程(AOP) 两章。

				// 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); 

使用此方面(aspect),@Transactional 可以把一个类或接口注解为事务性的,随后该类型定义的任何 公有 操作将拥有事务语义。单独一个 公有 方法的事务语义可以通过单独注解相应的方法定义。注意,如果注解了接口成员(不同于接口方法的实现方法),接口本身也应该被加上@Transactional注解。

要把AspectJ织入你的应用,你或者基于AspectJ构建你的应用(参考AspectJ Development Guide),或者采取“载入时织入”(load-time weaving),参考Section 6.7.4, “在Spring应用中使用AspectJ Load-time weaving(LTW)” 获得关于使用AspectJ进行“载入时织入”的讨论。

9.6. 默认事务设置

默认情况下事务设置(语义)如下:

  • 异常处理:RuntimeException 导致回滚,而普通异常(checked )则不会

  • 事务可读可写

  • 隔离级别:TransactionDefinition.ISOLATION_DEFAULT

  • 超时设置:TransactionDefinition.TIMEOUT_DEFAULT

org.springframework.transaction.TransactionDefinition接口的javadoc提供了关于上述设置的丰富的信息,这里就不再重复了。

9.7. 编程式事务管理

Spring提供两种方式的编程式事务管理:

  • 使用 TransactionTemplate

  • 直接使用一个 PlatformTransactionManager 实现

如果你选择编程式事务管理,Spring小组推荐你采用第一种方法(即使用TransactionTemplate)。第二种方法类似使用JTA的UserTransaction API (除了异常处理简单点儿)。

9.7.1. 使用 TransactionTemplate

TransactionTemplate采用与Spring中别的模板 同样的方法,如 JdbcTemplateHibernateTemplate。它使用回调机制,将应用代码从样板式的资源获取和释放代码中解放出来,不再有大量的try/catch/finally/try/catch代码块。同样,和别的模板类一样,TransactionTemplate 类的实例是线程安全的。

必须在事务上下文中执行的应用代码看起来像这样:(注意使用 TransactionCallback 可以有返回值)

Object result = tt.execute(new TransactionCallback() {
    public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
    }
});

如果不需要返回值,更方便的方式是创建一个TransactionCallbackWithoutResult的匿名类:

tt.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

回调方法内的代码可以通过调用 TransactionStatus对象的 setRollbackOnly() 方法来回滚事务。

想要使用 TransactionTemplate的应用类必须能访问一个PlatformTransactionManager(典型情况下通过依赖注入提供)。这样的类很容易做单元测试,只需要引入一个 PlatformTransactionManager 的伪类或桩类。这里没有JNDI查找、没有 静态 诡计,它是一个如此简单的接口。像往常一样,使用Spring给你的单元测试带来极大地简化。

9.7.2. 使用 PlatformTransactionManager

你也可以直接使用 org.springframework.transaction.PlatformTransactionManager的实现来管理事务。只需通过bean引用简单地传入一个 PlatformTransactionManager 实现,然后使用 TransactionDefinitionTransactionStatus 对象,你就可以启动一个事务,提交或回滚。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

9.8. 选择编程式事务管理还是声明式事务管理

当你只有很少的事务操作时,编程式事务管理通常比较合适。例如,如果你有一个web应用,其中只有特定的更新操作有事务要求,你可能不愿使用Spring或其他技术设置事务代理。这种情况下,使用 TransactionTemplate 可能 是个好办法。

另一方面,如果你的应用中存在大量事务操作,那么声明式事务管理通常是值得的。它将事务管理与业务逻辑分离,而且在Spring中配置也不难。使用Spring,而不是EJB CMT,声明式事务管理在配置上的成本极大地降低了。

9.9. 与特定应用服务器集成

一般来说,Spring的事务抽象与应用服务器是无关的。另外,使用Spring的JtaTransactionManager 类时,一种可选的方式是通过JNDI查询获得JTA UserTransactionTransactionManager 对象,其中后者可以被设置为自动探测,这时针对不同的应用服务器有不同的方式。能够直接访问 TransactionManager ,确实在很大程度上增强了事务语义,可以参考 JtaTransactionManager 类的javadoc获得更多细节。

9.9.1. BEA WebLogic

在一个使用WebLogic 7.0、8.1或更高版本的环境中,你一般会优先选用特定于Weblogic的WebLogicJtaTransactionManager 类来取代基础的JtaTransactionManager 类。在Weblogic环境中,该类提供了对Spring事务定义的完全支持,超过了标准的JTA语义。它的特性包括:支持事务名,支持为每个事务定义隔离级别,以及在任何环境下正确地恢复事务的能力。

9.9.2. IBM WebSphere

在WebSphere 5.1、5.0和4环境下,你可以使用Spring的WebSphereTransactionManagerFactoryBean 类。这是一个工厂类,通过WebSphere的 静态 访问方法获取到JTATransactionManager 实例。(这些静态方法在每个版本的WebSphere中都不同。)

一旦通过工厂bean获取到JTA TransactionManager 实例,就可以使用该实例装配一个Spring的 JtaTransactionManager bean,它封装了JTA UserTransaction,提供增强的事务语义。

请参考相关javadoc以获得完整信息。

9.10. 公共问题的解决方案

9.10.1. 对一个特定的 DataSource 使用错误的事务管理器

开发者需要按照需求仔细地选择正确的 PlatformTransactionManager 实现。理解Spring的事务抽象如何与JTA全局事务一起工作是非常重要的。使用得当,就不会有任何冲突:Spring仅仅提供一个直观的、可移植的抽象层。

如果你使用全局事务,你 必须 为你的所有事务操作使用Spring的 org.springframework.transaction.jta.JtaTransactionManager 类(或特定于某种应用服务器的子类)。否则Spring将试图在象容器数据源这样的资源上执行局部事务。这样的局部事务没有任何意义,好的应用服务器会把这些情况视为错误。

Sponsored by