17. Transaction Management

17.1 Spring Framework transaction management 简介

全面 transaction 支持是使用 Spring Framework 最引人注目的原因之一。 Spring Framework 为 transaction management 提供了一致的抽象,具有以下优点:

  • 跨越不同的 transaction API 的一致编程 model,例如 Java Transaction API(JTA),JDBC,Hibernate,Java Persistence API(JPA)和 Java Data Objects(JDO)。

  • 支持声明性 transaction management。

  • 比复杂 transaction API(如 JTA)更简单的纲领性 transaction management API。

  • 与 Spring 的数据访问抽象的完美整合。

以下部分描述了 Spring Framework 的 transaction value-adds 和技术。 (本章还包括对最佳实践的讨论,application server integration 和 common 的解决方案 problems.)

  • Spring Framework 的 transaction 支持 model 的优点描述了为什么要使用 Spring Framework 的 transaction 抽象而不是 EJB Container-Managed Transactions(CMT)或选择通过专有 API(如 Hibernate)驱动 local transactions。

  • 理解 Spring Framework transaction 抽象概述了核心 classes,并介绍了如何从各种来源配置和获取DataSource实例。

  • 使用 transactions 同步资源描述了 application code 如何确保正确创建,重用和清理资源。

  • 声明式 transaction management描述了对声明性 transaction management 的支持。

  • 程序化 transaction management涵盖对程序化(即明确编码)transaction management 的支持。

  • Transaction bound event描述了如何在 transaction 中使用 application events。

17.2 Spring Framework 的 transaction 支持 model 的优点

传统上,Java EE 开发人员对 transaction management 有两种选择:global 或 local transactions,这两种选择都有很大的局限性。 Global 和 local transaction management 将在接下来的两节中进行回顾,然后讨论 Spring Framework 的 transaction management 支持如何解决 global 和 local transaction 模型的局限性。

17.2.1 Global transactions

Global transactions 使您可以使用多个 transactional 资源,通常是关系数据库和消息队列。 application 服务器通过 JTA 管理 global transactions,这是一个使用繁琐的 API(部分原因是它的 exception model)。此外,JTA UserTransaction通常需要从 JNDI 获取,这意味着您还需要在 order 中使用 JNDI 来使用 JTA。显然,使用 global transactions 会限制 application code 的任何潜在重用,因为 JTA 通常仅在 application 服务器环境中可用。

以前,使用 global transactions 的首选方法是通过 EJB CMT(容器管理 Transaction):CMT 是一种声明式 transaction management(与程序性 transaction management 不同)。 EJB CMT 消除了对 transaction-related JNDI 查找的需要,但当然使用 EJB 本身需要使用 JNDI。它删除了大多数但不是全部需要编写 Java code 来控制 transactions。显着的缺点是 CMT 与 JTA 和应用程序服务器环境相关联。此外,仅当选择在 EJB 中实现业务逻辑时,或者至少在 transactional EJB 外观之后,它才可用。一般来说,EJB 的负面影响是如此之大,以至于这不是一个有吸引力的主张,特别是在面对声明性交易管理的令人信服的替代方案时。

17.2.2 Local transactions

Local transactions 是 resource-specific,例如与 JDBC 连接关联的 transaction。本地 transactions 可能更容易使用,但有明显的缺点:它们不能跨多个 transactional 资源工作。对于 example,使用 JDBC 连接管理 transactions 的 code 不能在 global JTA transaction 中运行。由于 application 服务器不参与 transaction management,因此无法确保跨多个资源的正确性。 (值得注意的是,大多数 applications 使用单个 transaction resource.)另一个缺点是本地 transactions 对编程 model 是侵入性的。

17.2.3 Spring Framework 的一致编程 model

Spring 解决了 global 和 local transactions 的缺点。它使 application 开发人员能够在任何环境中使用一致的编程 model。您只需编写一次 code,它就可以受益于不同环境中的不同 transaction management 策略。 Spring Framework 提供声明式和编程式 transaction management。大多数用户更喜欢声明式 transaction management,这在大多数情况下都是推荐的。

通过程序化的 transaction management,开发人员可以使用 Spring Framework transaction 抽象,它可以运行任何底层的 transaction 基础架构。使用首选的声明性 model,开发人员通常只编写与 transaction management 相关的很少或没有 code,因此不依赖于 Spring Framework transaction API 或任何其他 transaction API。


transaction management 需要 application 服务器吗?

Spring Framework 的 transaction management 支持改变了传统规则,即企业 Java application 何时需要 application 服务器。

特别是,对于通过 EJB 的声明性 transactions,您不需要 application 服务器。实际上,即使您的 application 服务器具有强大的 JTA 功能,您也可以认为 Spring Framework 的声明性 transactions 提供了比 EJB CMT 更强大的功能和更高效的编程 model。

通常,只有当 application 需要跨多个资源处理 transactions 时才需要 application 服务器的 JTA 功能,这不是许多 applications 的要求。许多 high-end applications 使用单个高度可伸缩的数据库(例如 Oracle RAC)。独立 transaction managers 如Atomikos Transactions和JOTM是其他选项。当然,您可能需要其他 application Server 功能,例如 Java Message Service(JMS)和 Java EE Connector Architecture(JCA)。

Spring Framework 让您可以选择何时将 application 扩展为完全加载的 application 服务器。使用 EJB CMT 或 JTA 的唯一替代方法是使用本地 transactions(例如 JDBC 连接上的那些)编写 code,并且如果在 global,container-managed transactions 中需要 code 到 run,则会面临大量的返工。使用 Spring Framework,只需要更改 configuration 文件中的某些 bean 定义,而不是 code。


17.3 了解 Spring Framework transaction 抽象

Spring transaction 抽象的 key 是 transaction 策略的概念。 transaction 策略由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),尽管它可以从 application code 中使用编程。因为PlatformTransactionManager是一个接口,所以可以根据需要轻松地模拟或存根。它与诸如 JNDI 之类的查找策略无关。 PlatformTransactionManager implementations 的定义与 Spring Framework IoC 容器中的任何其他 object(或 bean)相同。即使你使用 JTA,这个好处也使 Spring Framework transactions 成为一个有价值的抽象。 Transactional code 可以比直接使用 JTA 更容易测试。

再次与 Spring 的哲学保持一致,任何PlatformTransactionManager接口方法都可以抛出的TransactionException是未经检查的(也就是说,它扩展了java.lang.RuntimeException class)。 交易基础设施故障几乎总是致命的。在极少数情况下 application code 实际上可以从 transaction 失败中恢复,application 开发人员仍然可以选择捕获和处理TransactionException。重点是开发人员不会被迫这样做。

getTransaction(..)方法返回TransactionStatus object,具体取决于TransactionDefinition参数。返回的TransactionStatus可能表示新的 transaction,或者如果当前调用堆栈中存在匹配的 transaction,则可以表示现有的 transaction。后一种情况的含义是,与 Java EE transaction 上下文一样,TransactionStatus与执行线程相关联。

TransactionDefinition接口指定:

  • 传播:通常,transaction 范围内执行的所有 code 将在 transaction 中运行。但是,您可以选择在 event 中指定 transaction context 已存在时执行 transactional 方法的行为。对于 example,code 可以在现有的 transaction(common case)中继续 running;或者可以暂停现有的 transaction 并创建一个新的 transaction。 Spring 提供 EJB CMT 中熟悉的所有 transaction 传播选项。要阅读 Spring 中 transaction 传播的语义,请参阅第 17.5.7 节,“交易传播”。

  • 隔离:此 transaction 与其他 transactions 的工作隔离的程度。对于 example,这个 transaction 可以看到来自其他 transactions 的未提交的写入吗?

  • 超时:如果 transaction 在超时并由底层 transaction 基础结构自动回滚之前运行_长度。

  • Read-only status:当 code 读取但不修改数据时,可以使用 read-only transaction。 Read-only transactions 在某些情况下可能是一个有用的优化,例如当您使用 Hibernate 时。

这些设置反映了标准的 transactional 概念。如有必要,请参阅讨论 transaction 隔离级别和其他核心 transaction 概念的资源。理解这些概念对于使用 Spring Framework 或任何 transaction management 解决方案至关重要。

TransactionStatus接口为 transactional code 提供了一种简单的方法来控制 transaction 执行和查询 transaction 状态。这些概念应该是熟悉的,因为它们对所有 transaction API 都是通用的:

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

无论您是否在 Spring 中选择声明性或编程 transaction management,定义正确的PlatformTransactionManager implementation 都是绝对必要的。您通常通过依赖注入定义此 implementation。

PlatformTransactionManager implementations 通常需要了解它们工作的环境:JDBC,JTA,Hibernate 等。以下示例显示了如何定义本地PlatformTransactionManager implementation。 (这个 example 适用于普通的 JDBC.)

你定义了一个 JDBC 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 定义将具有DataSource定义的 reference。它看起来像这样:

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

如果在 Java EE 容器中使用 JTA,则使用通过 JNDI 获得的容器DataSource和 Spring 的JtaTransactionManager。这就是 JTA 和 JNDI 查找 version 的样子:

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

JtaTransactionManager不需要知道DataSource或任何其他特定资源,因为它使用容器的 global transaction management 基础结构。

dataSource bean 的上述定义使用jee命名空间中的<jndi-lookup/>标记。有关 schema-based configuration 的更多信息,请参阅第 41 章,XML Schema-based configuration,有关<jee/>标签的更多信息,请参阅标题为第 41.2.3 节,“jee schema”的部分。

您也可以轻松使用 Hibernate local transactions,如以下示例所示。在这种情况下,您需要定义 Hibernate LocalSessionFactoryBean,application code 将使用它来获取 Hibernate Session实例。

DataSource bean 定义类似于前面显示的本地 JDBC example,因此未在以下 example 中显示。

如果任何 non-JTA transaction manager 使用的DataSource是通过 JNDI 查找并由 Java EE 容器管理的,那么它应该是 non-transactional,因为 Spring Framework 而不是 Java EE 容器将管理 transactions。

在这种情况下,txManager bean 是HibernateTransactionManager类型。与DataSourceTransactionManager需要的DataSource相同,HibernateTransactionManager需要的 reference。

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.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.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

如果您正在使用 Hibernate 和 Java EE container-managed JTA transactions,那么您应该使用与之前的 JTA example 相同的JtaTransactionManager用于 JDBC。

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

如果您使用 JTA,那么无论您使用什么数据访问技术,无论是 JDBC,Hibernate JPA 还是任何其他支持的技术,您的 transaction manager 定义都将看起来相同。这是因为 JTA transactions 是 global transactions,它可以登记任何 transactional 资源。

在所有这些情况下,application code 不需要更改。您可以仅通过更改 configuration 来更改 transactions 的管理方式,即使该更改意味着从本地移动到 global transactions,反之亦然。

17.4 使用 transactions 同步资源

现在应该清楚如何创建不同的 transaction managers,以及它们如何链接到需要与 transactions 同步的相关资源(对于 example DataSourceTransactionManager到 JDBC DataSourceHibernateTransactionManager到 Hibernate SessionFactory等等)。本节描述 application code 如何直接或间接使用持久性 API(如 JDBC,Hibernate 或 JDO)确保正确创建,重用和清理这些资源。本节还讨论了如何通过相关的PlatformTransactionManager触发 transaction 同步(可选)。

17.4.1 High-level 同步方法

首选方法是使用 Spring 最高 level 模板的持久性 integration API,或者使用带有 transaction-aware factory beans 或代理的本机 ORM API 来管理本机资源工厂。这些 transaction-aware 解决方案在内部处理资源创建和重用,清理,资源的可选 transaction 同步和 exception 映射。因此,用户数据访问 code 不必解决这些任务,但可以完全专注于 non-boilerplate 持久性逻辑。通常,您使用本机 ORM API 或使用模板方法使用JdbcTemplate进行 JDBC 访问。这些解决方案将在本参考文档的后续章节中详细介绍。

17.4.2 Low-level 同步方法

__class,例如DataSourceUtils(对于 JDBC),EntityManagerFactoryUtils(对于 JPA),SessionFactoryUtils(对于 Hibernate),PersistenceManagerFactoryUtils(对于 JDO)等等存在于较低的 level。如果希望 application code 直接处理本机持久性 API 的资源类型,可以使用这些 classes 来确保获得正确的 Spring Framework-managed 实例,transactions(可选)同步,并且 process 中出现的 exceptions 被正确映射一致的 API。

例如,在 JDBC 的情况下,而不是在DataSource上调用getConnection()方法的传统 JDBC 方法,而是使用 Spring 的org.springframework.jdbc.datasource.DataSourceUtils class,如下所示:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有 transaction 已经与其同步(链接)了连接,则返回该实例。否则,方法调用会触发创建新连接,该连接(可选)与任何现有的 transaction 同步,并可在后续的同一 transaction 中重用。如上所述,任何SQLException都包含在 Spring Framework CannotGetJdbcConnectionException中,这是 Spring Framework 未经检查的 DataAccessExceptions 层次结构之一。这种方法为您提供了比从SQLException轻松获得的更多信息,并确保跨数据库的可移植性,甚至跨不同的持久性技术。

这种方法也可以在没有 Spring transaction management(transaction 同步是可选的)的情况下工作,因此无论您是否使用 Spring 进行 transaction management 都可以使用它。

当然,一旦你使用了 Spring 的 JDBC 支持,JPA 支持或 Hibernate 支持,你通常不会使用DataSourceUtils或其他帮助 classes,因为通过 Spring 抽象而不是直接使用相关的 API 会更快乐。例如,如果使用 Spring JdbcTemplatejdbc.object包来简化 JDBC 的使用,则在幕后进行正确的连接检索,您不需要编写任何特殊的 code。

17.4.3 TransactionAwareDataSourceProxy

在最低 level 存在TransactionAwareDataSourceProxy class。这是目标DataSource的代理,它包装目标DataSource以添加 Spring-managed transactions 的意识。在这方面,它类似于 Java EE 服务器提供的 transactional JNDI DataSource

除非必须调用现有的 code 并传递标准的 JDBC DataSource接口 implementation,否则几乎从来没有必要或不希望使用这个 class。在这种情况下,这个 code 可能是可用的,但参与 Spring 托管的 transactions。最好使用上面提到的更高 level 抽象来编写新的 code。

17.5 陈述 transaction management

大多数 Spring Framework 用户选择声明式 transaction management。此选项对 application code 的影响最小,因此最符合 non-invasive 轻量级容器的理想。

使用 Spring aspect-oriented 编程(AOP)可以实现 Spring Framework 的声明 transaction management,但是,由于 transactional code 与 Spring Framework 分布一起提供并且可以以样板方式使用,因此通常不必理解 AOP 概念有效使用这个 code。

Spring Framework 的声明性 transaction management 类似于 EJB CMT,因为您可以将 transaction 行为(或缺少行为)指定为单个方法 level。如有必要,可以在 transaction context 中进行setRollbackOnly()调用。两种 transaction management 之间的区别是:

  • 与绑定到 JTA 的 EJB CMT 不同,Spring Framework 的声明性 transaction management 可以在任何环境中使用。只需调整 configuration files,它就可以使用 JDBC,JPA,Hibernate 或 JDO 与 JTA transactions 或 local transactions 一起使用。

  • 您可以将 Spring Framework 声明 transaction management 应用于任何 class,而不仅仅是特殊的 classes,例如 EJB。

  • Spring Framework 提供声明性回滚规则, feature,没有 EJB 等价物。提供了对回滚规则的编程和声明性支持。

  • Spring Framework 使您可以使用 AOP 自定义 transactional 行为。对于 example,您可以在 transaction 回滚的情况下插入自定义行为。您还可以添加任意建议以及 transactional 建议。使用 EJB CMT,除了setRollbackOnly()之外,您不能影响容器的 transaction management。

  • Spring Framework 不支持跨 remote calls 传播 transaction 上下文,high-end application 服务器也是如此。如果您需要此 feature,我们建议您使用 EJB。但是,在使用这样的 feature 之前要仔细考虑,因为通常情况下,人们不希望 transactions 对 span remote calls。


TransactionProxyFactoryBean 在哪里?

Spring 2.0 及以上版本中的声明性 transaction configuration 与以前版本的 Spring 有很大不同。主要区别在于不再需要配置TransactionProxyFactoryBean beans。

pre-Spring 2.0 configuration 样式仍然是 100%有效 configuration;认为新的<tx:tags/>只是代表你定义TransactionProxyFactoryBean beans。


回滚规则的概念很重要:它们使您能够指定哪些 exceptions(和 throwables)应该导致自动回滚。您可以在 configuration 中以声明方式指定,而不是在 Java code 中。因此,虽然您仍然可以在TransactionStatus object 上调用setRollbackOnly()来回滚当前的 transaction,但大多数情况下您可以指定MyApplicationException必须始终导致回滚的规则。此选项的显着优势是业务 objects 不依赖于 transaction 基础结构。例如,它们通常不需要 import Spring transaction API 或其他 Spring API。

虽然 EJB 容器默认行为会自动回滚系统 exception(通常是运行时 exception)上的 transaction,但 EJB CMT 不会在应用 exception 上自动回滚 transaction(即,除了java.rmi.RemoteException之外的已检查 exception)。虽然声明性 transaction management 的 Spring 默认行为遵循 EJB 约定(回滚仅在未经检查的 exceptions 上自动回放),但定制此行为通常很有用。

17.5.1 了解 Spring Framework 的声明 transaction implementation

仅仅告诉您使用@Transactional annotation 注释 classes,将@EnableTransactionManagement添加到 configuration,然后期望您了解它是如何工作的是不够的。本节解释#

关于 Spring Framework 的声明性 transaction 支持,最重要的概念是这个支持是通过 AOP 代理启用的,transactional 建议是由元数据驱动的(目前是 XML-或 annotation-based)。 AOP 与 transactional 元数据的组合产生一个 AOP 代理,它使用TransactionInterceptor和适当的PlatformTransactionManager implementation 来驱动方法调用的 transactions。

Spring AOP 涵盖在第 11 章,使用 Spring 进行面向对象编程中。

从概念上讲,在 transactional 代理上调用方法看起来像这样......

17.5.2 声明 transaction implementation 的示例

考虑以下接口及其伴随 implementation。此 example 使用FooBar classes 作为占位符,以便您可以专注于 transaction 用法,而无需关注特定的域 model。出于本示例的目的,DefaultFooService class 在每个实现的方法的主体中抛出UnsupportedOperationException实例的事实是好的;它允许您查看 transactions 已创建,然后回滚以响应UnsupportedOperationException实例。

// the service interface that we want to make transactional

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
// an implementation of the above interface

package x.y.service;

public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }

    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }

    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

}

假设FooService接口的前两个方法getFoo(String)getFoo(String, String)必须在具有 read-only 语义的 transaction 的 context 中执行,并且其他方法insertFoo(Foo)updateFoo(Foo)必须在具有 read-write 语义的 transaction 的 context 中执行。以下配置将在接下来的几段中详细说明。

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

检查前面的 configuration。你想创建一个服务 object,fooService bean,_ transnsactional。要应用的 transaction 语义封装在<tx:advice/>定义中。 <tx:advice/>定义读作“...所有以'get'开头的方法都是在 read-only transaction 的 context 中执行,所有其他方法都是用默认的 transaction 语义执行”。 <tx:advice/>标记的transaction-manager属性设置为PlatformTransactionManager bean 的 name,它将驱动 transactions,在本例中为txManager bean。

如果要连接的PlatformTransactionManager的 bean name 具有 name transactionManager,则可以省略 transactional 通知(<tx:advice/>)中的transaction-manager属性。如果要连接的PlatformTransactionManager bean 具有任何其他 name,则必须显式使用transaction-manager属性,如前面的 example 中所示。

<aop:config/>定义确保txAdvice bean 定义的 transactional 通知在程序中的适当位置执行。首先,定义一个切入点,该切入点与FooService接口(fooServiceOperation)中定义的任何操作的执行相匹配。然后使用顾问程序将切入点与txAdvice相关联。结果表明,在执行fooServiceOperation时,txAdvice定义的建议将是 run。

<aop:pointcut/>元素中定义的表达式是 AspectJ 切入点表达式;有关 Spring 中切入点表达式的更多详细信息,请参阅第 11 章,使用 Spring 进行面向对象编程。

common 要求是创建整个服务层 transactional。执行此操作的最佳方法是将切入点表达式更改为 match 服务层中的任何操作。例如:

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

在此 example 中,假设所有服务接口都在x.y.service包中定义;有关详细信息,请参阅第 11 章,使用 Spring 进行面向对象编程。

现在我们已经分析了 configuration,你可能会问自己,“好吧......但是所有这些 configuration 实际上做了什么?”。

上面的 configuration 将用于围绕从fooService bean 定义创建的 object 创建 transactional 代理。代理将使用 transactional 配置进行配置,以便在代理上调用适当的方法时,transaction 将被启动,挂起,标记为 read-only,依此类推,具体取决于与该方法关联的 transaction 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());
    }
}

running 前面程序的输出将类似于以下内容。 (LogFJ Service 输出和 DefaultFooService class 的 insertFoo(..)方法抛出的 UnsupportedOperationException 的堆栈跟踪已被截断为 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 [[emailprotected]] 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 [[emailprotected]]
[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)

17.5.3 回滚声明式 transaction

上一节概述了如何在 application 中以声明方式为 classes(通常是服务层 classes)指定 transactional 设置的基础知识。本节介绍如何以简单的声明方式控制 transactions 的回滚。

向 Spring Framework 的 transaction 基础结构指示 transaction 的工作将被回滚的推荐方法是从当前在 transaction 的 context 中执行的 code 中抛出Exception。 Spring Framework 的 transaction infrastructure code 将捕获任何未处理的Exception,因为它会调用调用堆栈,并确定是否标记 transaction 以进行回滚。

在默认的 configuration 中,Spring Framework 的 transaction infrastructure code 仅在运行时标记 transaction for rollback,unchecked exceptions;也就是说,当抛出的 exception 是RuntimeException的实例或子类时。 (Error s 也将 - 默认情况下 - 导致回滚)。从 transactional 方法抛出的已检查 exceptions 不会导致默认 configuration 中的回滚。

您可以准确配置哪些Exception类型标记 transaction 以进行回滚,包括已检查的 exceptions。以下 XML 代码段演示了如何为已检查的 application-specific Exception类型配置回滚。

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

如果您不希望在抛出 exception 时回滚 transaction,也可以指定“无回滚规则”。以下 example 告诉 Spring Framework 的 transaction 基础设施即使面对未处理的InstrumentNotFoundException也要提交服务员 transaction。

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

当 Spring Framework 的 transaction 基础结构捕获 exception 并且它参考配置的回滚规则以确定是否标记 transaction 进行回滚时,最强匹配规则获胜。因此,在以下 configuration 的情况下,除之外的任何 exception 都会导致服务员 transaction 的回滚。

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    </tx:attributes>
</tx:advice>

您还可以以编程方式指示所需的回滚。虽然非常简单,但这个 process 非常具有侵入性,并且将 code 紧密地耦合到 Spring Framework 的 transaction 基础设施:

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

强烈建议您尽可能使用声明性方法进行回滚。如果您绝对需要程序化回滚,则可以使用程序化回滚,但它的使用方式可以实现干净的 POJO-based architecture。

17.5.4 为不同的 beans 配置不同的 transactional 语义

考虑具有多个服务层 objects 的场景,并且您希望对它们中的每一个应用完全不同的 transactional configuration。您可以通过定义具有不同pointcutadvice-ref属性值的不同<aop:advisor/>元素来执行此操作。

作为比较,首先假设您的所有服务层 classes 都在根x.y.service包中定义。要使所有 beans 成为在该包(或子包中)中定义的 classes 实例并且名称以Service结尾的具有默认的 transactional configuration,您将编写以下内容:

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

以下 example 显示了如何使用完全不同的 transactional 设置配置两个不同的 beans。

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

17.5.5 < hh:// +206+ .h >设置

本节总结了可以使用<tx:advice/>标记指定的各种 transactional 设置。默认的<tx:advice/>设置为:

  • 传播设置是REQUIRED.

  • 隔离 level 是DEFAULT.

  • Transaction 是 read/write。

  • Transaction timeout 默认为基础 transaction 系统的默认超时,如果不支持超时,则为 none。

  • 任何RuntimeException触发回滚,任何已检查的Exception都没有。

您可以更改这些默认设置;嵌套在<tx:advice/><tx:attributes/>标签中的<tx:method/>标签的各种属性总结如下:

表格 1_.< hh:// +220+ 3 >设置

属性需要?默认描述
name 方法 name(s)与 transaction 属性相关联。通配符(*)字符可用于将相同的 transaction 属性设置与多个方法相关联;对于 example,get*handle*on*Event等。
propagation没有需要Transaction 传播行为。
isolation没有默认Transaction isolation level。仅适用于传播 REQUIRED 或 REQUIRES_NEW。
timeout没有-1Transaction timeout(秒)。仅适用于传播 REQUIRED 或 REQUIRES_NEW。
read-only没有Read/write 与 read-only transaction。仅适用于 REQUIRED 或 REQUIRES_NEW。
rollback-for没有 Exception(s)触发回滚; comma-delimited。对于 example,com.foo.MyBusinessException,ServletException.
no-rollback-for没有 Exception(s)不触发回滚; comma-delimited。对于 example,com.foo.MyBusinessException,ServletException.

17.5.6 使用 @Transactional

除_tra_action configuration 的 XML-based 声明方法之外,您还可以使用 annotation-based 方法。直接在 Java source code 中声明 transaction 语义会使声明更接近受影响的 code。没有太多的过度耦合的危险,因为无论如何,用于交易使用的 code 几乎总是以这种方式部署。

标准javax.transaction.Transactional annotation 也被支持为 Spring 自己的 annotation 的 drop-in 替换。有关更多详细信息,请参阅 JTA 1.2 文档。

使用@Transactional annotation 提供的 ease-of-use 最好用 example 说明,后面的文字对此进行了解释。考虑以下 class 定义:

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

如上所述在 class level 中使用,annotation 指示声明 class(及其子类)的所有方法的默认值。或者,每个方法都可以单独注释。请注意,class-level annotation 不适用于 class 层次结构的祖先 classes;在这种情况下,需要在 order 中本地重新声明方法以参与 subclass-level annotation。

当上述 POJO 在 Spring IoC 容器中定义为 bean 时,bean 实例可以通过仅添加一行__ XML 配置来实现 transactional:

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

如果要连接的PlatformTransactionManager的 bean name 具有 name transactionManager,则可以省略<tx:annotation-driven/>标记中的transaction-manager属性。如果您想要

如果使用基于 Java 的 configuration,则@EnableTransactionManagement annotation 提供等效支持。只需将 annotation 添加到@Configuration class 即可。有关详细信息,请参阅 javadocs。


方法可见性和@Transactional

使用代理时,应将@Transactional annotation 仅应用于具有公共可见性的方法。如果使用@Transactional annotation 注释 protected,private 或 package-visible 方法,则不会引发错误,但带注释的方法不会显示已配置的 transactional 设置。如果需要注释 non-public 方法,请考虑使用 AspectJ(见下文)。


您可以将@Transactional annotation 放在接口定义,接口上的方法,class 定义或 class 上的公共方法之前。但是,仅仅存在@Transactional annotation 不足以激活 transactional 行为。 @Transactional annotation 只是一些可由@Transactional -aware 的运行时基础结构使用的元数据,并且可以使用元数据来配置具有 transactional 行为的相应 beans。在前面的 example 中,<tx:annotation-driven/>元素会切换 transactional 行为。

Spring 建议您只使用@Transactional annotation 注释具体的 classes(以及具体 classes 的方法),而不是注释接口。您当然可以将@Transactional annotation 放在接口(或接口方法)上,但这只能在您使用 interface-based 代理时按预期工作。 Java annotations 不是从接口继承的事实意味着如果您使用 class-based 代理(proxy-target-class="true")或 weaving-based aspect(mode="aspectj"),则代理和编织基础结构无法识别 transaction 设置,并且 object 将不会被包装在一个 transactional 代理,这将是非常糟糕的。

在代理模式(默认设置)下,只拦截通过代理进入的外部方法 calls。这意味着 self-invocation 实际上是一个目标 object 中调用 target object 的另一个方法的方法,即使被调用的方法用@Transactional标记,也不会在运行时导致实际的 transaction。此外,必须完全初始化代理以提供预期的行为,因此您不应该在初始化 code,i.e 中依赖此 feature。 @PostConstruct

考虑使用 AspectJ 模式(请参阅下面的 table 中的 mode 属性),如果您希望 self-invocations 也包含 transactions。在这种情况下,首先不会有代理;相反,目标 class 将在 order 中编织(即,它的字节 code 将被修改),以便在任何类型的方法上将@Transactional转换为运行时行为。

表格 1_._Anotnotation driven transaction settings

XML 属性Annotation 属性默认描述
transaction-managerN/A(见TransactionManagementConfigurer javadocs)transactionManagertransaction manager 要使用的 Name。仅当 transaction manager 的 name 不是transactionManager时才需要,如上面的 example 中所示。
modemode代理默认模式“proxy”使用 Spring 的 AOP framework 处理带注释的 beans 代理(遵循代理语义,如上所述,仅应用于通过代理进入的方法 calls)。替代模式“aspectj”用 Spring 的 AspectJ transaction aspect 编织受影响的 classes,修改目标 class byte code 以应用于任何类型的方法调用。 AspectJ 编织需要在 classpath 中启用 spring-aspects.jar 以及启用 load-time 编织(或 compile-time 编织)。 (有关如何设置 load-time weaving.)的详细信息,请参阅名为“Spring configuration”的部分
proxy-target-classproxyTargetClass仅适用于代理模式。控制为使用@Transactional annotation 注释的 class 创建的 transactional 代理类型。如果proxy-target-class属性设置为true,则会创建 class-based 代理。如果proxy-target-classfalse或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理人的详细检查,请参阅第 11.6 节,“代理机制” types.)
orderorderOrdered.LOWEST_PRECEDENCE定义应用于使用@Transactional注释的 beans 的 transaction 建议的 order。 (有关与 AOP 建议的 ordering 相关的规则的更多信息,请参阅名为“建议订购”的部分 .)没有指定的 ordering 意味着 AOP 子系统确定建议的 order。

处理@Transactional annotations 的默认建议模式是“proxy”,它允许仅通过代理拦截 calls;同一 class 中的 local calls 不能以这种方式截获。对于更高级的拦截模式,请考虑结合 compile/load-time 编织切换到“aspectj”模式。

proxy-target-class属性控制为使用@Transactional annotation 注释的 classes 创建的 transactional 代理类型。如果proxy-target-class设置为true,则会创建 class-based 个代理。如果proxy-target-classfalse或者省略了该属性,则会创建标准 JDK interface-based 代理。 (有关不同代理的讨论,请参阅第 11.6 节,“代理机制” types.)

@EnableTransactionManagement<tx:annotation-driven/>只在 beans 中查找@Transactional在它们定义的相同 application context 中。这意味着,如果你在WebApplicationContext中为DispatcherServlet放置 annotation driven configuration,它只检查控制器中的@Transactional beans,而不是你的服务。有关更多信息,请参见第 22.2 节,“DispatcherServlet”。

在评估方法的 transactional 设置时,派生的位置优先。在以下 example 的情况下,DefaultFooService class 在 class level 中使用 read-only transaction 的设置进行注释,但同一 class 中updateFoo(Foo)方法上的@Transactional annotation 优先于 class level 中定义的 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
    }
}

@Transactional 设置

@Transactional annotation 是元数据,指定接口,class 或方法必须具有 transactional 语义;对于 example,“在调用此方法时启动一个全新的 read-only transaction,暂停任何现有的 transaction”。默认的@Transactional设置如下:

  • 传播设置为PROPAGATION_REQUIRED.

  • 隔离 level 是ISOLATION_DEFAULT.

  • Transaction 是 read/write。

  • Transaction timeout 默认为基础 transaction 系统的默认超时,如果不支持超时,则为 none。

  • 任何RuntimeException触发回滚,任何已检查的Exception都没有。

可以更改这些默认设置; @Transactional annotation 的各种 properties 总结在以下 table 中:

表 17.3. @Transactional 设置

属性类型描述
可选限定符,指定要使用的 transaction manager。
传播enum:Propagation可选的传播设置。
isolationenum:Isolation可选隔离 level。仅适用于传播 REQUIRED 或 REQUIRES_NEW。
timeoutint(以秒为单位)可选的 transaction 超时。仅适用于传播 REQUIRED 或 REQUIRES_NEW。
readOnlybooleanRead/write 与 read-only transaction。仅适用于 REQUIRED 或 REQUIRES_NEW。
rollbackFor_A_ray Class objects,必须从Throwable.派生可选的 exception exception classes 必须导致回滚。
rollbackForClassName_Alass 类的 class 名称。 Classes 必须从Throwable.派生exception classes 名称的可选 array 必须导致回滚。
noRollbackFor_A_ray Class objects,必须从Throwable.派生可选 array 的 exception classes,不得导致回滚。
noRollbackForClassNameArray 的String class 名称,必须从Throwable.派生可选 array 的 exception classes 名称,不得导致回滚。

目前,您无法明确控制 transaction 的 name,其中'name'表示 transaction name 将显示在 transaction 监视器中(如果适用)(对于 example,WebLogic 的 transaction 监视器)和 logging 输出。对于声明式 transactions,transaction name 始终是 fully-qualified class name“。” transactionally-advised class 的方法 name。例如,如果BusinessService class 的handlePayment(..)方法启动 transaction,则 transaction 的 name 将为:com.foo.BusinessService.handlePayment

多个 Transaction Managers with @Transactional

大多数 Spring applications 只需要一个 transaction manager,但是在某些 application 中你可能需要多个独立的 transaction managers。 @Transactional annotation 的 value 属性可用于选择性地指定要使用的PlatformTransactionManager的标识。这可以是 bean name 或 transaction manager bean 的限定符 value。对于 example,使用限定符表示法,以下 Java code

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}

可以与 application context 中的以下 transaction manager bean 声明结合使用。

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

在这种情况下,TransactionalService上的两个方法将在单独的 transaction managers 下运行,由“order”和“account”限定符区分。如果未找到特定限定的 PlatformTransactionManager bean,仍将使用默认的<tx:annotation-driven> target bean name transactionManager

自定义快捷方式注释

如果您发现在多种不同方法中重复使用@Transactional的相同属性,则Spring 的 meta-annotation 支持允许您为特定用例定义自定义快捷方式注释。对于 example,定义以下注释

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

允许我们将上一节中的 example 编写为

public class TransactionalService {

    @OrderTx
    public void setSomething(String name) { ... }

    @AccountTx
    public void doSomething() { ... }
}

这里我们使用语法来定义 transaction manager 限定符,但也可以包含传播行为,回滚规则,超时等。

17.5.7 交易传播

本节描述了 Spring 中 transaction 传播的一些语义。请注意,本节不是 transaction 传播的介绍;相反,它详细介绍了 Spring 中有关 transaction 传播的一些语义。

在 Spring-managed transactions 中,请注意物理和逻辑 transactions 之间的区别,以及传播设置如何应用于此差异。

必填

PROPAGATION_REQUIRED

当传播设置为PROPAGATION_REQUIRED时,将为应用该设置的每个方法创建逻辑 transaction 范围。每个这样的逻辑 transaction 范围可以单独确定 rollback-only status,外部 transaction 范围在逻辑上独立于内部 transaction 范围。当然,在标准PROPAGATION_REQUIRED行为的情况下,所有这些范围将映射到相同的物理 transaction。因此,内部 transaction 范围中的 rollback-only 标记集确实会影响外部 transaction 实际提交的机会(正如您所期望的那样)。

但是,在内部 transaction 范围设置 rollback-only 标记的情况下,外部 transaction 尚未决定回滚本身,因此回滚(由内部 transaction 范围静默触发)是意外的。此时抛出相应的UnexpectedRollbackException。这是预期的行为,因此 transaction 的调用者永远不会被误导,假设在实际上没有执行提交。因此,如果内部 transaction(外部调用者不知道)默认将 transaction 标记为 rollback-only,则外部调用者仍然会 calls commit。外部调用者需要接收UnexpectedRollbackException以清楚地指示已执行回滚。

要求新

PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRED相比,PROPAGATION_REQUIRES_NEW始终对每个受影响的 transaction 范围使用独立的物理 transaction,从不参与外部范围的现有 transaction。在这样的安排中,底层资源 transactions 是不同的,因此可以独立提交或回滚,外部 transaction 不受内部 transaction 的回滚状态的影响,并且内部 transaction 的锁在完成后立即释放。这样一个独立的内部 transaction 也可以声明它自己的隔离 level,timeout 和 read-only 设置,永远不会继承外部 transaction 的特性。

嵌套

PROPAGATION_NESTED使用单个物理 transaction,它可以回滚到多个保存点。这种部分回滚允许内部 transaction 范围触发其范围的回滚,外部 transaction 能够继续物理 transaction,尽管已经回滚了一些操作。此设置通常映射到 JDBC 保存点,因此仅适用于 JDBC 资源 transactions。见 Spring 的DataSourceTransactionManager

17.5.8 为 transactional 操作提供建议

假设您要执行 transactional 和一些基本的分析建议。你如何在<tx:annotation-driven/>的 context 中实现这一点?

当您调用updateFoo(Foo)方法时,您希望看到以下操作:

  • 配置的分析 aspect 启动。

  • Transactional 建议执行。

  • 建议 object 上的方法执行。

  • Transaction 提交。

  • 分析 aspect 报告整个 transactional 方法调用的确切持续时间。

本章不涉及详细解释 AOP(除非适用于 transactions)。有关以下 AOP configuration 和 AOP 的详细介绍,请参见第 11 章,使用 Spring 进行面向对象编程。

这是上面讨论的简单分析 aspect 的 code。建议的 ordering 通过Ordered接口控制。有关建议 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>

上述 configuration 的结果是fooService bean,它在所需的 order 中应用了 profiling 和 transactional 方面。您可以以类似的方式配置任意数量的其他方面。

以下 example 实现与上面相同的设置,但使用纯粹的 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">

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

上述 configuration 的结果将是一个fooService bean,它在该 order 中对其应用了分析和 transactional 方面。如果你希望在进行 transactional 建议之后的 transactional 建议之后执行分析建议,那么你只需要交换分析 aspect bean 的order property 的 value,使它高于 transactional advice 的 order 值。

您可以以类似的方式配置其他方面。

17.5.9 在 AspectJ 中使用 @Transactional

也可以通过 AspectJ aspect 在 Spring 容器外部使用 Spring Framework 的@Transactional支持。为此,首先使用@Transactional annotation 注释 classes(以及可选的 classes'方法),然后将 application 与spring-aspects.jar文件中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect链接(编织)。 aspect 还必须配置 transaction manager。您当然可以使用 Spring Framework 的 IoC 容器来处理 dependency-injecting aspect。配置 transaction management aspect 的最简单方法是使用<tx:annotation-driven/>元素并将mode属性指定为aspectj,如第 17.5.6 节,“使用@Transactional”中所述。因为我们将重点放在 Spring 容器之外的 applications running 上,所以我们将向您展示如何以编程方式进行操作。

在继续之前,您可能需要分别阅读第 17.5.6 节,“使用@Transactional”和第 11 章,使用 Spring 进行面向对象编程。

// 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 时,必须注释 implementation class(该 class 中的 and/or 方法),而不是 class 实现的接口(如果有)。 AspectJ 遵循 Java 的规则,即接口上的注释不会被继承。

class 上的@Transactional annotation 指定了 class 中执行任何公共方法的默认 transaction 语义。

class 中的方法上的@Transactional annotation 覆盖 class annotation(如果存在)给出的默认 transaction 语义。无论可见性如何,都可以注释任何方法。

要使用AnnotationTransactionAspect编写 applications,您必须使用 AspectJ 构建 application(请参阅AspectJ 开发指南)或使用 load-time 编织。有关使用 AspectJ 进行 load-time 编织的讨论,请参阅第 11.8.4 节,“在 Spring Framework 中使用 AspectJ 编织 Load-time”。

17.6 Programmatic transaction management

Spring Framework 提供了两种程序化 transaction management 方法:

  • 使用TransactionTemplate

  • 直接使用PlatformTransactionManager implementation。

Spring 团队通常建议使用TransactionTemplate进行程序化 transaction management。第二种方法类似于使用 JTA UserTransaction API,尽管 exception 处理不那么麻烦。

17.6.1 使用 TransactionTemplate

TransactionTemplate采用与其他 Spring 模板相同的方法,例如JdbcTemplate。它使用回调方法,使 application code 免于必须进行样板获取和 transactional 资源的释放,并导致 code 被意图驱动,因为编写的 code 仅关注开发人员想要做的事情。

正如您将在后面的示例中看到的那样,使用TransactionTemplate绝对会将您与 Spring 的 transaction 基础结构和 API 相结合。程序化 transaction management 是否适合您的开发需求是您必须自己做出的决定。

Application code 必须在 transactional context 中执行,并且将明确使用TransactionTemplate,如下所示。作为 application 开发人员,您编写TransactionCallback implementation(通常表示为匿名内部 class),其中包含您需要在 transaction 的 context 中执行的 code。然后,将自定义TransactionCallback的实例传递给TransactionTemplate上公开的execute(..)方法。

public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // the code in this method executes in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果没有 return value,请使用带有匿名 class 的方便的TransactionCallbackWithoutResult class,如下所示:

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

回调中的 Code 可以通过调用提供的TransactionStatus object 上的setRollbackOnly()方法来回滚 transaction:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessExeption ex) {
            status.setRollbackOnly();
        }
    }
});

指定 transaction 设置

您可以在TransactionTemplate上以编程方式或在 configuration 中指定 transaction 设置,例如传播模式,隔离 level,超时等。 TransactionTemplate实例默认具有默认 transactional 设置。以下 example 显示了特定TransactionTemplate:的 transactional 设置的编程自定义

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        // the transaction settings can be set here explicitly if so desired
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        this.transactionTemplate.setTimeout(30); // 30 seconds
        // and so forth...
    }
}

以下 example 使用 Spring XML configuration 定义带有一些自定义 transactional 设置的TransactionTemplate。然后可以将sharedTransactionTemplate注入到所需数量的服务中。

<bean id="sharedTransactionTemplate"
        class="org.springframework.transaction.support.TransactionTemplate">
    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
    <property name="timeout" value="30"/>
</bean>"

最后,TransactionTemplate class 的实例是线程安全的,因为实例不维护任何会话 state。但是TransactionTemplate实例确实维护 configuration state,所以虽然 class 可以共享TransactionTemplate的单个实例,但如果 class 需要使用TransactionTemplate具有不同的设置(对于 example,不同的隔离 level),那么你需要创建两个_c不同的TransactionTemplate个实例。

17.6.2 使用 PlatformTransactionManager

您也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理 transaction。只需通过 bean reference 将的_i实现传递给 bean。然后,使用TransactionDefinitionTransactionStatus objects,您可以启动 transactions,回滚和提交。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done programmatically
def.setName("SomeTxName");
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);

17.7 在程序性和声明式 transaction management 之间进行选择

只有少数 transactional 操作,程序化 transaction management 通常是一个很好的 idea。例如,如果您的 web application 只需要 transactions 用于某些更新操作,您可能不希望使用 Spring 或任何其他技术设置 transactional 代理。在这种情况下,使用TransactionTemplate可能是一个很好的方法。能够显式设置 transaction name 也只能使用 transaction management 的编程方法来完成。

另一方面,如果你的 application 有很多 transactional 操作,声明 transaction management 通常是值得的。它使 transaction management 不受业务逻辑的影响,并且不难配置。当使用 Spring Framework 而不是 EJB CMT 时,声明性 transaction management 的 configuration 成本大大降低。

17.8 Transaction bound event

从 Spring 4.2 开始,event 的 listener 可以绑定到 transaction 的一个阶段。典型的示例是在 transaction 成功完成时处理 event:当当前 transaction 的结果对 listener 实际上很重要时,这允许 events 使用更灵活。

注册常规 event listener 是通过@EventListener annotation 完成的。如果需要将它绑定到 transaction,请使用@TransactionalEventListener。执行此操作时,listener 将默认绑定到 transaction 的提交阶段。

我们来一个 example 来说明这个概念。假设一个 component 发布一个 order 创建 event,我们想要定义一个 listener,只有在成功提交它的 transaction 成功提交后才应该处理该 event:

@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
        ...
    }
}

TransactionalEventListener annotation 公开了一个phase属性,该属性允许我们自定义 listener 应绑定到 transaction 的哪个阶段。有效阶段是BEFORE_COMMITAFTER_COMMIT(默认),AFTER_ROLLBACKAFTER_COMPLETION,它们聚合 transaction 完成(无论是提交还是回滚)。

如果没有 transaction running,则根本不会调用 listener,因为我们无法遵守所需的语义。但是,可以通过将 annotation 的fallbackExecution属性设置为true来覆盖该行为。

17.9 Application server-specific integration

Spring 的 transaction 抽象通常是 application server 不可知的。此外,Spring 的JtaTransactionManager class 可以选择性地为 JTA UserTransactionTransactionManagerobjects 执行 JNDI 查找,自动检测后者 object 的位置,该位置因 application 服务器而异。访问 JTA TransactionManager允许增强 transaction 语义,特别是支持 transaction 暂停。有关详细信息,请参阅JtaTransactionManager javadocs。

Spring 的JtaTransactionManager是 Java EE application 服务器上 run 的标准选择,并且已知可以在所有 common 服务器上运行。诸如 transaction suspension 之类的高级功能也适用于许多服务器 - 包括 GlassFish,JBoss 和 Geronimo - 无需任何特殊的 configuration 配置。但是,对于完全支持的 transaction 暂停和进一步的高级 integration,Spring 为 WebLogic Server 和 WebSphere 提供了特殊的适配器。以下各节将讨论这些适配器。

对于标准方案(包括 WebLogic Server 和 WebSphere),请考虑使用方便的<tx:jta-transaction-manager/> configuration 元素。配置后,此元素会自动检测基础服务器并选择可用于平台的最佳 transaction manager。这意味着您不必显式配置 server-specific adapter classes(如以下部分所述);相反,它们是自动选择的,标准JtaTransactionManager为默认回退。

17.9.1 IBM WebSphere

在 WebSphere 6.1.0.9 及更高版本上,推荐使用的 Spring JTA transaction manager 是WebSphereUowTransactionManager。这个特殊的适配器利用了 IBM 的UOWManager API,它可以在 WebSphere Application Server 6.1.0.9 及更高版本中使用。使用此适配器,IBM 正式支持 Spring-driven transaction 暂停(由PROPAGATION_REQUIRES_NEW启动 suspend/resume)。

17.9.2 Oracle WebLogic Server

在 WebLogic Server 9.0 或更高版本上,通常使用WebLogicJtaTransactionManager而不是 stock JtaTransactionManager class。普通JtaTransactionManager的这个特殊 WebLogic-specific 子类支持 WebLogic-managed transaction 环境中 Spring 的 transaction 定义的全部功能,超出了标准的 JTA 语义:Features 包括 transaction 名称,per-transaction 隔离级别,以及在所有情况下 transactions 的正确恢复。

17.10 解决 common 问题

17.10.1 对特定 DataSource 使用错误的 transaction manager

根据您选择的 transactional 技术和要求,使用正确的PlatformTransactionManager implementation。如果使用得当,Spring Framework 只提供简单易用的抽象。如果您使用的是 global transactions,则必须使用org.springframework.transaction.jta.JtaTransactionManager class(或application server-specific subclass)来进行所有 transactional 操作。否则,transaction 基础结构会尝试对容器DataSource实例等资源执行本地 transactions。这样的本地 transactions 没有意义,一个好的 application 服务器将它们视为错误。

17.11 更多资源

有关 Spring Framework 的 transaction 支持的更多信息:

  • Spring 中的分布式 transactions,有和没有 XA是一个 JavaWorld 演示文稿,其中 Spring 的 David Syer 在 Spring applications 中引导您完成分布式 transactions 的七种模式,其中三种模式使用 XA,另外四种没有。

  • Java Transaction 设计策略是一本来自InfoQ 中文站的书,它为 Java 中的 transactions 提供 well-paced 介绍。它还包括如何使用 Spring Framework 和 EJB3 配置和使用 transactions 的 side-by-side 示例。