Spring 事务管理

Chapter 9. 事务管理

9.1. 简介

Spring框架引人注目的重要因素之一是它全面的事务支持。Spring框架提供了一致的事务管理抽象,这带来了以下好处:

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

  • 支持 声明式事务管理

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

  • 非常好地整合Spring的各种数据访问抽象

这章被分成几个小节,每一节将描述一种Spring框架事务支持的附加值或技术。本章末尾讨论了一些关于事务管理的最佳实践(比如,如何在编程式和声明式事务管理之间做选择)。

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

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

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

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

9.2. 动机

事务管理究竟是否需要应用服务器?

Spring框架对事务管理的支持极大地改变了传统上认为J2EE应用需要应用服务器的认识。

这种改变尤其在于你不需要仅仅为了通过EJB来使用声明式事务而使用应用服务器。事实上,即使你的应用服务器拥有强大的JTA功能,你也有充分的理由可以发现,Spring框架的声明式事务提供了比EJB CMT更加强大、高效的编程模型。

一般来说,只有当你需要支持多个事务性资源时,你才需要应用服务器的JTA功能。而大多数应用并不需要能够处理跨越多种资源。许多高端应用使用单一的、高伸缩性的数据库,比如Oracle 9i RAC。

(当然,也许你需要应用服务器的其他功能,比如JMS或JCA。)

最重要的一点是,使用Spring,你可以选择何时把你的应用迁移到全功能的应用服务器。用硬编码去实现本地事务来替代EJB CMT或JTA,处理JDBC连接,或者还需要使用硬编码来处理那些全局的、受到容器管理的事务,这样的日子将一去不复返了。使用Spring,你仅需要改动配置文件,而不必改动你的代码。

传统上,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>

如果我们在J2EE容器里使用JTA,就像示例中 'dataAccessContext-jta.xml' 文件所示,我们将通过JNDI和Spring的 JtaTransactionManager 来获取一个容器管理的 DataSource。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:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.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

上面 '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定义同上例类似(这里不再展示)。不过,如果是一个JEE容器提供的 DataSource,它将由JEE容器自身,而不是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 DataSource, HibernateTransactionManager 对应到Hibernate的 SessionFactory 等)。可是,剩下的问题是,直接或间接地使用一种持久化API(JDBC、Hibernate、JDO等)的应用代码,如何确保通过相关的 PlatformTransactionManager 来恰当地获取并操作资源,来满足事务同步,这些操作包括:创建、复用、清理 和 触发(可能没有)。

9.4.1. 高层次方案

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

9.4.2. 低层次方案

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

例如,在JDBC环境下,你不再使用传统的调用 DataSource 的 getConnection() 方法的方式,而是使用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的 JdbcTemplate 或 jdbc.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。然而,不要轻易使用这些特性。通常我们并不希望事务跨越远程调用。

TransactionProxyFactoryBean在哪儿?

Spring2.0及以后的版本中声明式事务的配置与之前的版本有相当大的不同。主要差异在于不再需要配置TransactionProxyFactoryBean了。

Spring2.0之前的旧版本风格的配置仍然是有效的;你可以简单地认为新的<tx:tags/>替你定义了TransactionProxyFactoryBean。

回滚规则的概念比较重要:它使我们能够指定什么样的异常(和throwable)将导致自动回滚。我们在配置文件中声明式地指定,无须在Java代码中。同时,我们仍旧可以通过调用 TransactionStatus 的 setRollbackOnly() 方法编程式地回滚当前事务。通常,我们定义一条规则,声明 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

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

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

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

}
    <!-- 上述接口的一个实现 --> 

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

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

  <!-- 这是我们将要配置并使它具有事务性的Service对象 --> 
  <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="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 (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

事实上,如果 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的消息和从 DefaultFooService 的 insertFoo(..) 方法抛出的 UnsupportedOperationException 异常堆栈信息被省略了)。

    <!-- Spring容器开始启动... --> 
[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. 回滚

在前面的章节里,概述了如何在你的应用里为类特别是服务层的类指定事务性的基本方法。这一章将描述在一个简单的声明式配置中如何你才能控制事务的回滚。

一个容易的(和被推荐的)方法是在Spring框架的事务架构里指出当context的事务里的代码抛出 Exception 时事务进行回滚。Spring框架的事务基础架构代码将从调用的堆栈里捕获到任何未处理的 Exception,并将标识事务将回滚。

然而,请注意Spring框架的事务基础架构代码将默认地 在抛出运行时和unchecked exceptions时才标识事务回滚。 也就是说,当抛出一个 RuntimeException 或其子类例的实例时。(Errors 也一样 - 默认地 - 标识事务回滚。)从事务方法中抛出的Checked exceptions将 被标识进行事务回滚。

就是这些默认的设置;严格规定了哪些 Exception 类型将被标识进行事务回滚。 下面的XML配置片断里示范了如何配置一个checked,应用程序指定的 Exception 类型来标识事务回滚。

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

第二方法是在Spring框架的事务架构里通过 编程式 方式指出一个事务将被回滚。 虽然这个非常简单,但是这个方法对于Spring框架的事务架构来说,在你的代码是高入侵的和紧藕合的 下面的代码片断里示范了Spring框架管理事务的编程式回滚:

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

强烈推荐你尽可能地使用声明式事务回滚方法。 编程式方法的回滚对你来说是可见,如果你需要它你就可以使用,但是使用它就直接违反了在你的应用中使用一个纯基于POJO的模型。

9.5.4. 为不同的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-2.0.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.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>

下面的配置示例演示了两个不同的bean拥有完全不同的事务配置。

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

    <aop:config>

        <aop:pointcut id="defaultServiceOperation" expression="execution(* x.y.service.*Service.*(..))"/>
        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
        
        <aop:pointcut id="noTxServiceOperation" expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

    </aop:config>

    <!-- this bean will be transactional (c.f. 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>

9.5.5. <tx:advice/> 有关的设置

这一节里将描述通过 <tx:advice/> 标签来指定不同的事务性设置。默认的 <tx:advice/> 设置如下:

  • 事务传播设置是 REQUIRED

  • 隔离级别是 DEFAULT

  • 事务是 读/写

  • 事务超时默认是依赖于事务系统的,或者事务超时没有被支持。

  • 任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚

这些默认的设置当然也是可以被改变的。 <tx:advice/> 和 <tx:attributes/> 标签里的 <tx:method/> 各种属性设置总结如下:

Table 9.1. <tx:method/> 有关的设置

属性 是否需要? 默认值 描述
name  

与事务属性关联的方法名。通配符(*)可以用来指定一批关联到相同的事务属性的方法。 如:'get*'、'handle*'、'on*Event'等等。

propagation REQUIRED 事务传播行为
isolation DEFAULT 事务隔离级别
timeout -1 事务超时的时间(以秒为单位)
read-only false 事务是否只读?
rollback-for  

将被触发进行回滚的 Exception(s);以逗号分开。 如:'com.foo.MyBusinessException,ServletException'

no-rollback-for  

被触发进行回滚的 Exception(s);以逗号分开。 如:'com.foo.MyBusinessException,ServletException'

9.5.6. 使用 @Transactional

Note

@Transactional 注解及其支持类所提供的功能最低要求使用Java 5(Tiger)。

除了基于XML文件的声明式事务配置外,你也可以采用基于注解式的事务配置方法。直接在Java源代码中声明事务语义的做法让事务声明和将受其影响的代码距离更近了,而且一般来说不会有不恰当的耦合的风险,因为,使用事务性的代码几乎总是被部署在事务环境中。

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

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

当上述的POJO定义在Spring IoC容器里时,上述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-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.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

实际上,如果你用 'transactionManager' 来定义 PlatformTransactionManager bean的名字的话,你就可以忽略 <tx:annotation-driven/> 标签里的 'transaction-manager' 属性。 如果 PlatformTransactionManager bean你要通过其它名称来注入的话,你必须用 'transaction-manager' 属性来指定它,如上所示。

方法的可见度和 @Transactional

@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别 @Transactional 注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 <tx:annotation-driven/>元素的出现 开启 了事务行为。

Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。

Note

当使用 @Transactional 风格的进行声明式事务定义时,你可以通过 <tx:annotation-driven/> 元素的 "proxy-target-class" 属性值来控制是基于接口的还是基于类的代理被创建。如果 "proxy-target-class" 属值被设置为 "true",那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 "proxy-target-class" 属值被设置为 "false" 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。

在多数情形下,方法的事务设置将被优先执行。在下列情况下,例如: DefaultFooService 类被注解为只读事务,但是,这个类中的 updateFoo(Foo) 方法的 @Transactional 注解的事务设置将优先于类级别注解的事务设置。

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

9.5.6.1. @Transactional 有关的设置

@Transactional 注解是用来指定接口、类或方法必须拥有事务语义的元数据。 如:“当一个方法开始调用时就开启一个新的只读事务,并停止掉任何现存的事务”。 默认的 @Transactional 设置如下:

  • 事务传播设置是 PROPAGATION_REQUIRED

  • 事务隔离级别是 ISOLATION_DEFAULT

  • 事务是 读/写

  • 事务超时默认是依赖于事务系统的,或者事务超时没有被支持。

  • 任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚

这些默认的设置当然也是可以被改变的。 @Transactional 注解的各种属性设置总结如下:

Table 9.2. @Transactional 注解的属性

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

9.5.7. 插入事务操作

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

我们调用 updateFoo(Foo) 方法时希望这样:

  • 配置的剖析切面(profiling aspect)开始启动,

  • 然后进入事务通知(根据配置创建一个新事务或加入一个已经存在的事务),

  • 然后执行原始对象的方法,

  • 然后事务提交(我们假定这里一切正常),

  • 最后剖析切面报告整个事务方法执行过程花了多少时间。

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;

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

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

上面配置的结果将获得到一个拥有剖析和事务方面的 按那样的顺序 应用于它上面的 'fooService' bean。 许多附加的方面的配置将一起达到这样的效果。

最后,下面的一些示例演示了使用纯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-2.0.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.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>

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

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

9.5.8. 结合AspectJ使用 @Transactional

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

Note

在我们继续之前,你可能需要好好读一下前面的Section 9.5.6, “使用 @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); 
Note

使用此切面(aspect),你必须在 实现 类(和/或类里的方法)、而 不是 类的任何所实现的接口上面进行注解。AspectJ遵循Java的接口上的注解 不被继承 的规则。

类上的 @Transactional 注解指定了类里的任何 public 方法执行的默认事务语义。

类里的方法的 @Transactional 将覆盖掉类注解的默认事务语义(如何存在的话)。 public、protected和默认可见的方法可能都被注解。直接对 protected和默认可见的方法进行注解,让这些方法在执行时去获取所定义的事务划分是唯一的途径。

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

9.6. 编程式事务管理

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

  • 使用 TransactionTemplate

  • 直接使用一个 PlatformTransactionManager 实现

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

9.6.1. 使用 TransactionTemplate

TransactionTemplate 采用与Spring中别的 模板 同样的方法,如 JdbcTemplate 和 HibernateTemplate。它使用回调机制,将应用代码从样板式的资源获取和释放代码中解放出来,不再有大量的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.6.2. 使用 PlatformTransactionManager

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

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.7. 选择编程式事务管理还是声明式事务管理

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

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

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

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

9.8.1. BEA WebLogic

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

9.8.2. IBM WebSphere

在WebSphere 5.1、5.0和4环境下,你可以使用Spring的 WebSphereTransactionManagerFactoryBean 类。这是一个工厂类,通过WebSphere的 静态 访问方法获取到JTA TransactionManager 实例。(这些静态方法在每个版本的WebSphere中都不同。)一旦通过工厂bean获取到JTA TransactionManager 实例,就可以使用该实例装配一个Spring的 JtaTransactionManager bean,它封装了JTA UserTransaction,提供增强的事务语义。请参考相关javadoc以获得完整信息。

9.9. 公共问题的解决方案

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

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

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

9.10. 更多的资源

从下面的链接中你可以找到更多关于Spring框架事务支持方面的资源

  • 来自 InfoQ 的 Java事务设计策略(Java Transaction Design Strategies) 是一本介绍Java事务方面相当好的书。同时它也包含了如何在Spring框架和EJB3中配置与使用事务的对比示例。

http://www.redsaga.com/spring_ref/2.0/html/transaction.html#transaction-declarative

你可能感兴趣的:(spring,AOP,Hibernate,bean,应用服务器,attributes)