17. Transaction Management

17.1 Spring Framework 事务 Management 简介

全面的事务支持是使用 Spring Framework 的最令人信服的原因。 Spring 框架为事务 Management 提供了一致的抽象,具有以下优点:

  • 跨不同事务 API(例如 Java 事务 API(JTA),JDBC,Hibernate,Java 持久性 API(JPA)和 Java 数据对象(JDO))的一致编程模型。

  • 支持声明式 TransactionManagement

  • programmatic事务 Management 的 API 比 JTA 等复杂的事务 API 更简单。

  • 与 Spring 的数据访问抽象的出色集成。

以下各节描述了 Spring 框架的 Transaction 增值和技术。 (本章还讨论了最佳实践,应用程序服务器集成以及常见问题的解决方案.)

17.2 Spring 框架的事务支持模型的优点

传统上,Java EE 开发人员在事务 Management 中有两种选择:* global local *事务,两者都有深远的局限性。下两节将回顾全局和本地事务 Management,然后讨论 Spring 框架的事务 Management 支持如何解决全局和本地事务模型的局限性。

17.2.1GlobalTransaction

全局事务使您可以使用多个事务资源,通常是关系数据库和消息队列。应用服务器通过 JTAManagement 全局事务,该 JTA 是一个麻烦的 API(部分是由于其异常模型)。此外,通常需要从 JNDI 派生 JTA UserTransaction,这意味着您需要使用 JNDI 才能使用 JTA。显然,使用全局事务将限制应用程序代码的任何潜在重用,因为 JTA 通常仅在应用程序服务器环境中可用。

以前,使用全局事务的首选方式是通过 EJB * CMT *(容器 Management 的事务):CMT 是“声明式事务 Management”(与“程序化事务 Management”不同)的一种形式。 EJB CMT 消除了与事务相关的 JNDI 查找的需要,尽管当然使用 EJB 本身必须使用 JNDI。它消除了编写 Java 代码来控制事务的大部分(但不是全部)需求。重大缺点是 CMT 与 JTA 和应用程序服务器环境相关联。而且,仅当选择在 EJB 中或至少在事务性 EJB 幕后实现业务逻辑时才可用。通常,EJB 的缺点很大,以至于这不是一个有吸引力的主张,尤其是面对声明式事务 Management 的强制选择时。

17.2.2 本地 Transaction

本地事务是特定于资源的,例如与 JDBC 连接关联的事务。本地事务可能更易于使用,但有很多缺点:它们不能跨多个事务资源工作。例如,使用 JDBC 连接 Management 事务的代码不能在全局 JTA 事务中运行。由于应用程序服务器不参与事务 Management,因此它无法帮助确保多个资源之间的正确性。 (值得注意的是,大多数应用程序使用单个事务资源.)另一个缺点是本地事务侵入了编程模型。

17.2.3 Spring Framework 的一致编程模型

Spring 解决了 Global 和本地 Transaction 的弊端。它使应用程序开发人员可以在任何环境中使用一致*的编程模型。您只需编写一次代码,即可从不同环境中的不同事务 Management 策略中受益。 Spring 框架提供了声明式和程序化事务 Management。大多数用户喜欢声明式事务 Management,这在大多数情况下建议使用。

通过程序化事务 Management,开发人员可以使用 Spring Framework 事务抽象,该抽象可以在任何基础事务基础架构上运行。使用首选的声明性模型,开发人员通常只需编写很少或没有编写与事务 Management 相关的代码,因此不依赖于 Spring Framework 事务 API 或任何其他事务 API。

Do you need an application server for transaction management?

Spring Framework 的事务 Management 支持更改了有关企业 Java 应用程序何时需要应用程序服务器的传统规则。

特别是,您不需要仅通过 EJB 进行声明式事务的应用程序服务器。实际上,即使您的应用服务器具有强大的 JTA 功能,您也可以决定 Spring 框架的声明式事务比 EJB CMT 提供更多的功能和更高效的编程模型。

通常,仅当您的应用程序需要处理跨多个资源的事务时才需要应用程序服务器的 JTA 功能,而这并不是许多应用程序所必需的。许多高端应用程序使用单个高度可扩展的数据库(例如 Oracle RAC)来代替。其他选择包括独立事务 Management 器,例如Atomikos TransactionsJOTM。当然,您可能需要其他应用程序服务器功能,例如 Java 消息服务(JMS)和 Java EE 连接器体系结构(JCA)。

Spring Framework 为您提供何时将应用程序扩展到完全加载的应用程序服务器的选择。不再使用 EJB CMT 或 JTA 的唯一选择是使用本地事务(例如 JDBC 连接上的事务)编写代码,并且如果需要使该代码在全局的,容器 Management 的事务中运行,则面临大量的工作。使用 Spring Framework,只需更改配置文件中的某些 Bean 定义,而无需更改代码。

17.3 了解 Spring Framework 事务抽象

Spring 事务抽象的关键是事务策略的概念。 org.springframework.transaction.PlatformTransactionManager界面定义了一种 Transaction 策略:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

尽管可以在您的应用程序代码中programmatically使用它,但它主要是一个服务提供商接口(SPI)。由于PlatformTransactionManager是一个* interface *,因此可以根据需要轻松地对其进行模拟或存根。它与 JNDI 之类的查找策略无关。 PlatformTransactionManager实现的定义与 Spring Framework IoC 容器中的任何其他对象(或 bean)一样。即使您使用 JTA,仅此一项优点也使 Spring Framework 事务成为有价值的抽象。与直接使用 JTA 相比,可以更轻松地测试事务代码。

再次与 Spring 的原理保持一致,可以通过PlatformTransactionManager接口的任何方法抛出的TransactionException是未选中的*(即,它扩展了java.lang.RuntimeException类)。Transaction 基础架构故障几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获并处理TransactionException。突出的一点是,不强制开发人员这样做。

getTransaction(..)方法根据TransactionDefinition参数返回TransactionStatus对象。返回的TransactionStatus可能代表新事务,或者如果当前调用堆栈中存在匹配的事务,则代表现有事务。后一种情况的含义是,与 Java EE 事务上下文一样,TransactionStatus与执行的“线程”相关联。

TransactionDefinition接口指定:

    • Propagation *:通常,在事务范围内执行的所有代码都将在该事务中运行。但是,您可以选择在已经存在事务上下文的情况下执行事务方法时指定行为。例如,代码可以在现有事务中 continue 运行(常见情况);或者可以暂停现有 Transaction 并创建新 Transaction。 * Spring 提供了 EJB CMT *熟悉的所有事务传播选项。要了解有关 Spring 中事务传播的语义,请参阅第 17.5.7 节“事务传播”
  • 隔离:此事务与其他事务的工作隔离的程度。例如,此事务能否看到其他事务未提交的写入?

    • Timeout *:此事务在超时之前将运行多长时间,并由基础事务基础结构自动回滚。
  • 只读状态:当您的代码读取但不修改数据时,可以使用只读事务。在某些情况下,例如使用 Hibernate 时,只读事务可能是有用的优化。

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

TransactionStatus界面为事务代码提供了一种控制事务执行和查询事务状态的简单方法。这些概念应该很熟悉,因为它们对于所有事务 API 都是通用的:

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

无论您在 Spring 中选择声明式还是程序化事务 Management,定义正确的PlatformTransactionManager实现都是绝对必要的。通常,您可以通过依赖注入来定义此实现。

PlatformTransactionManager实现通常需要了解其工作环境:JDBC,JTA,Hibernate 等。以下示例说明如何定义本地PlatformTransactionManager实现。 (此示例适用于纯 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定义。它看起来像这样:

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

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

<?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或任何其他特定资源,因为它使用了容器的全局事务 Management 基础结构。

Note

dataSource bean 的以上定义使用jee名称空间中的<jndi-lookup/>标记。有关基于架构的配置的更多信息,请参见第 41 章,基于 XML Schema 的配置,而有关<jee/>标签的更多信息,请参见标题为第 41.2.3 节“ jee 模式”的部分。

您还可以轻松使用 Hibernate 本地事务,如以下示例所示。在这种情况下,您需要定义一个 Hibernate LocalSessionFactoryBean,您的应用程序代码将使用它来获取 Hibernate Session实例。

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

Note

如果任何非 JTA 事务 Management 器使用的DataSource是通过 JNDI 查找并由 Java EE 容器 Management 的,则它应该是非事务性的,因为 Spring 框架而不是 Java EE 容器将 Management 事务。

在这种情况下,txManager bean 是HibernateTransactionManager类型。就像DataSourceTransactionManager需要引用DataSource一样,HibernateTransactionManager也需要引用SessionFactory

<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 容器 Management 的 JTA 事务,则应该简单地使用与前面的 JDBC JTA 示例相同的JtaTransactionManager

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

Note

如果您使用 JTA,那么无论您使用哪种数据访问技术(无论是 JDBC,Hibernate JPA 或任何其他受支持的技术),事务 Management 器定义都将看起来相同。这是由于 JTA 事务是全局事务,它可以征用任何事务资源。

在所有这些情况下,无需更改应用程序代码。您可以仅通过更改配置来更改事务的 Management 方式,即使更改意味着从本地事务转移到全局事务,反之亦然。

17.4 将资源与事务同步

现在应该清楚如何创建不同的事务 Management 器,以及如何将它们链接到需要同步到事务的相关资源(例如,DataSourceTransactionManager到 JDBC DataSourceHibernateTransactionManager到 Hibernate SessionFactory等等)。本节介绍如何使用持久性 API(例如 JDBC,Hibernate 或 JDO)直接或间接地应用程序代码确保正确创建,重用和清理这些资源。本节还讨论了如何通过相关的PlatformTransactionManager(可选)触发事务同步。

17.4.1 高级同步方法

首选方法是使用 Spring 基于最高级别模板的持久性集成 API 或将本机 ORM API 与具有事务意识的工厂 bean 或代理一起使用,以 Management 本机资源工厂。这些支持事务的解决方案在内部处理资源的创建和重用,清理,资源的可选事务同步以及异常 Map。因此,用户数据访问代码不必解决这些任务,而可以将重点纯粹放在非样板持久性逻辑上。通常,您使用本机 ORM API 或通过使用JdbcTemplate采取模板方法进行 JDBC 访问。这些解决方案将在本参考文档的后续章节中详细介绍。

17.4.2 低级同步方法

诸如DataSourceUtils(对于 JDBC),EntityManagerFactoryUtils(对于 JPA),SessionFactoryUtils(对于 Hibernate),PersistenceManagerFactoryUtils(对于 JDO)等类在较低的级别上存在。当您希望应用程序代码直接处理本机持久性 API 的资源类型时,可以使用这些类来确保获得正确的 Spring FrameworkManagement 的实例,事务(可选)同步,并且流程中发生的异常是正确 Map 到一致的 API。

例如,对于 JDBC,您可以使用 Spring 的org.springframework.jdbc.datasource.DataSourceUtils类,而不是使用传统的 JDBC 方法在DataSource上调用getConnection()方法。

Connection conn = DataSourceUtils.getConnection(dataSource);

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

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

当然,一旦使用了 Spring 的 JDBC 支持,JPA 支持或 Hibernate 支持,您通常将不愿使用DataSourceUtils或其他帮助程序类,因为与直接使用相关 API 相比,通过 Spring 抽象进行工作会更加快乐。例如,如果您使用 Spring JdbcTemplatejdbc.object包来简化 JDBC 的使用,则正确的连接检索将在后台进行,并且您无需编写任何特殊代码。

17.4.3 TransactionAwareDataSourceProxy

最低级别是TransactionAwareDataSourceProxy类。这是目标DataSource的代理,该代理包装了目标DataSource以增加对 SpringManagement 的事务的了解。在这方面,它类似于 Java EE 服务器提供的事务性 JNDI DataSource

除非必须调用现有代码并通过标准的 JDBC DataSource接口实现,否则使用此类绝对几乎是没有必要的。在这种情况下,该代码可能可用,但可以参与 Spring 托管的事务。最好使用上述更高级别的抽象来编写新代码。

17.5 声明式事务 Management

Note

大多数 Spring Framework 用户选择声明式事务 Management。此选项对应用程序代码的影响最小,因此与“非侵入性”轻型容器的理想状态最一致。

Spring 框架的声明式事务 Management 可以通过 Spring 面向方面的编程(AOP)来实现,尽管由于事务方面的代码随 Spring 框架发行版一起提供并且可以以样板方式使用,所以通常不必理解 AOP 概念有效利用此代码。

Spring 框架的声明式事务 Management 与 EJB CMT 相似,因为您可以指定事务行为(或不存在事务)直至单个方法级别。如有必要,可以在事务上下文中进行setRollbackOnly()调用。两种类型的事务 Management 之间的区别是:

  • 与绑定到 JTA 的 EJB CMT 不同,Spring 框架的声明式事务 Management 可在任何环境中工作。只需调整配置文件,它就可以使用 JDBC,JPA,Hibernate 或 JDO 使用 JTA 事务或本地事务。

  • 您可以将 Spring Framework 声明式事务 Management 应用于任何类,而不仅限于 EJB 之类的特殊类。

  • Spring 框架提供了声明性的rollback rules,功能,没有 EJB 等效项。提供了对回滚规则的编程和声明性支持。

  • Spring Framework 使您可以使用 AOP 定制事务行为。例如,在事务回滚的情况下,您可以插入自定义行为。您还可以添加任意建议以及事务建议。使用 EJB CMT,除了setRollbackOnly()之外,您无法影响容器的事务 Management。

  • Spring 框架不像高端应用程序服务器那样支持跨远程调用的事务上下文传播。如果需要此功能,建议您使用 EJB。但是,在使用这种功能之前,请仔细考虑,因为通常情况下,您不希望事务跨越远程调用。

Where is TransactionProxyFactoryBean?

Spring 2.0 及更高版本中的声明式事务配置与 Spring 的早期版本有很大不同。主要区别在于不再需要配置TransactionProxyFactoryBean bean。

Spring 2.0 之前的配置样式仍然是 100%有效的配置;认为新的<tx:tags/>只是代表您定义TransactionProxyFactoryBean bean。

回滚规则的概念很重要:它们使您能够指定应导致自动回滚的异常(和可抛出对象)。您可以在配置中而不是在 Java 代码中以声明方式指定。因此,尽管您仍然可以在TransactionStatus对象上调用setRollbackOnly()来回滚当前事务,但是大多数情况下,您可以指定MyApplicationException必须始终导致回滚的规则。此选项的主要优点是业务对象不依赖于事务基础结构。例如,他们通常不需要导入 Spring 事务 API 或其他 Spring API。

尽管 EJB 容器的默认行为会在系统异常(通常是运行时异常)下自动回滚事务,但是 EJB CMT 不会在应用程序异常(即java.rmi.RemoteException以外的已检查异常)下自动回滚事务。尽管 Spring 声明式事务 Management 的默认行为遵循 EJB 约定(仅针对未检查的异常会自动回滚),但自定义此行为通常很有用。

17.5.1 了解 Spring 框架的声明式事务实现

仅告诉您使用@TransactionalComments 对类进行 Comments,将@EnableTransactionManagement添加到您的配置中,然后希望您了解其全部工作原理是不够的。本节说明在发生与事务相关的问题时,Spring 框架的声明式事务基础结构的内部工作方式。

关于 Spring 框架的声明式事务支持,要把握的最重要的概念是启用此支持通过 AOP 代理,并且事务通知由元数据(当前基于 XML 或基于 Comments)驱动。 AOP 与事务性元数据的组合产生了一个 AOP 代理,该代理使用TransactionInterceptor结合适当的PlatformTransactionManager实现来在方法调用周围驱动事务。

从概念上讲,在事务代理上调用方法看起来像这样……

tx

17.5.2 声明式事务实现示例

考虑以下接口及其附带的实现。本示例使用FooBar类作为占位符,以便您可以专注于事务使用而不关注特定的域模型。就本示例而言,DefaultFooService类在每个已实现方法的主体中抛出UnsupportedOperationException实例的事实是好的;它允许您查看已创建的事务,然后响应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)必须在具有只读语义的事务上下文中执行,而其他方法insertFoo(Foo)updateFoo(Foo)必须在具有 read-语义的事务上下文中执行写语义。以下几节将详细说明以下配置。

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

检查前面的配置。您要使服务对象fooService bean 具有事务性。要应用的事务语义封装在<tx:advice/>定义中。 <tx:advice/>的定义为“ …以'get'开头的所有方法都将在只读事务的上下文中执行,而所有其他方法将以默认的事务语义执行”。 <tx:advice/>标签的transaction-manager属性设置为PlatformTransactionManager bean 的名称,该 bean 将“驱动”事务,在本例中为txManager bean。

Tip

如果要连接的PlatformTransactionManager的 bean 名称具有transactionManager,则可以在事务通知(<tx:advice/>)中省略transaction-manager属性。如果要连接的PlatformTransactionManager bean 具有其他名称,则必须如上例所示显式使用transaction-manager属性。

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

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

一个普遍的要求是使整个服务层具有事务性。最好的方法是更改切入点表达式以匹配服务层中的任何操作。例如:

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

Note

在此示例中,假定您的所有服务接口都在x.y.service包中定义;有关更多详细信息,请参见第 11 章,使用 Spring 进行面向方面的编程.

现在我们已经分析了配置,您可能会问自己:“ 好吧……但是所有这些配置实际上是做什么的?”。

上面的配置将用于围绕从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());
    }
}

运行前面的程序的输出将类似于以下内容。 (为清楚起见,DefaultFooService 类的 insertFoo(..)方法抛出的 UnsupportedOperationException 的 Log4J 输出和堆栈跟踪已被截断。)

<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [[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 回退声明式事务

上一节概述了如何在应用程序中声明性地指定类(通常是服务层类)的事务设置的基础。本节介绍如何以简单的声明方式控制事务的回滚。

向 Spring Framework 的事务基础结构指示要回滚事务的推荐方法是从事务上下文中当前正在执行的代码中抛出Exception。当 Spring Framework 的事务基础结构代码在调用堆栈中冒泡时,它将捕获所有未处理的Exception,并确定是否将事务标记为回滚。

在其默认配置中,Spring Framework 的事务基础结构代码“ ”仅在运行时未经检查的异常情况下将事务回滚;也就是说,当抛出的异常是RuntimeException的实例或子类时。 (默认情况下,Error也会导致回滚)。从事务方法中抛出的检查异常不会导致默认配置中的回滚。

您可以准确配置哪些Exception类型将事务标记为回滚,包括已检查的异常。以下 XML 代码段演示了如何为选中的,特定于应用程序的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>

如果您不希望引发异常时回滚事务,则还可以指定“无回滚规则”。下面的示例告诉 Spring Framework 的事务基础结构,即使面对未处理的InstrumentNotFoundException,也要提交伴随的事务。

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

当 Spring Framework 的事务基础结构捕获到异常并咨询已配置的回滚规则以确定是否将事务标记为回滚时,最强匹配规则获胜。因此,在以下配置的情况下,除了InstrumentNotFoundException之外的任何异常都将导致附带事务的回滚。

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

您还可以以编程方式指示所需的回滚。尽管非常简单,但此过程具有很大的侵入性,并将您的代码紧密耦合到 Spring Framework 的事务基础结构:

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

强烈建议您尽可能使用声明性方法进行回滚。如果您绝对需要它,则可以使用程序化回滚,但是面对一个干净的基于 POJO 的体系结构,它的用法就不那么理想了。

17.5.4 为不同的 bean 配置不同的事务语义

考虑以下场景:您有许多服务层对象,并且您希望对其应用一个“完全不同的”事务配置。您可以通过定义具有不同pointcutadvice-ref属性值的不同<aop:advisor/>元素来实现此目的。

作为比较,首先假定所有服务层类都在根x.y.service包中定义。要使所有在该包(或子包)中定义的类实例且名称以Service结尾的 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.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>

以下示例说明如何使用完全不同的事务设置配置两个不同的 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.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<tx:advice/>设置

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

  • Propagation settingREQUIRED.

  • 隔离级别为DEFAULT.

  • 事务是读/写。

  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则默认为无。

  • 任何RuntimeException都会触发回滚,而所有选中的Exception都不会触发。

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

表 17.1. <>设置

AttributeRequired?DefaultDescription
nameYes 与事务属性相关联的方法名称。通配符(*)可用于将相同的事务属性设置与多种方法相关联。例如get*handle*on*Event等。
propagationNoREQUIRED事务传播行为。
isolationNoDEFAULT事务隔离级别。仅适用于 REQUIRED 或 REQUIRES_NEW 传播。
timeoutNo-1事务超时(秒)。仅适用于 REQUIRED 或 REQUIRES_NEW 传播。
read-onlyNofalse读/写与只读事务。仅适用于 REQUIRED 或 REQUIRES_NEW。
rollback-forNo Exception(s)触发回滚;以逗号分隔。例如com.foo.MyBusinessException,ServletException.
no-rollback-forNo Exception(s)不会不会触发回滚;以逗号分隔。例如com.foo.MyBusinessException,ServletException.

17.5.6 使用@Transactional

除了基于 XML 的声明式方法进行事务配置外,还可以使用基于 Comments 的方法。直接在 Java 源代码中声明事务语义会使声明更接近受影响的代码。不存在过度耦合的危险,因为原本打算以事务方式使用的代码几乎总是以这种方式部署。

Note

还支持使用标准的javax.transaction.TransactionalComments 来代替 Spring 自己的 Comments。请参阅 JTA 1.2 文档以获取更多详细信息。

使用@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);
}

在上面的类级别使用,Comments 指示声明类(及其子类)的所有方法的默认值。另外,每种方法都可以单独 Comments。注意,类级别的 Comments 不适用于类层次结构中的祖先类。在这种情况下,需要在本地重新声明方法,以参与子类级别的 Comments。

当上述 POJO 在 Spring IoC 容器中定义为 bean 时,可以通过仅添加 XML 配置的一行来使 bean 实例具有事务性:

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required -->

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (this dependency is defined somewhere else) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

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

</beans>

Tip

如果要连接的PlatformTransactionManager的 bean 名称具有名称transactionManager,则可以省略<tx:annotation-driven/>标记中的transaction-manager属性。如果要依赖注入的PlatformTransactionManager bean 具有其他名称,则必须像前面的示例中一样显式使用transaction-manager属性。

Note

如果您使用的是基于 Java 的配置,则@EnableTransactionManagement注解提供等效的支持。只需将 Comments 添加到@Configuration类。有关完整的详细信息,请参见 javadocs。

Method visibility and @Transactional

使用代理时,应仅将@TransactionalComments 应用于具有* public *可见性的方法。如果使用@TransactionalComments 对受保护的,私有的或程序包可见的方法进行 Comments,则不会引发任何错误,但是带 Comments 的方法不会显示已配置的事务设置。如果需要 Comments 非公共方法,请考虑使用 AspectJ(请参见下文)。

您可以将@Transactional注解放在接口定义,接口上的方法,类定义或类上的* public *方法之前。但是,仅存在@TransactionalComments 不足以激活事务行为。 @TransactionalComments 只是可以由@Transactional感知的某些运行时基础结构使用的元数据,并且可以使用该元数据来配置具有事务行为的适当 Bean。在前面的示例中,<tx:annotation-driven/>元素“打开”Transaction 行为。

Tip

Spring 建议您仅使用@TransactionalComments 对具体类(以及具体类的方法)进行 Comments,而不是对接口进行 Comments。您当然可以在接口(或接口方法)上放置@Transactional注解,但这仅在您使用基于接口的代理时才可以使用。 JavaComments 不是从接口继承的事实意味着,如果您使用的是基于类的代理(proxy-target-class="true")或基于编织的方面(mode="aspectj"),那么代理和编织基础结构将无法识别事务设置,并且该对象将不会包装在事务代理中,后者肯定是* bad *。

Note

在代理模式(默认设置)下,仅拦截通过代理传入的外部方法调用。这意味着,实际上,即使被调用的方法标有@Transactional,自调用实际上也不会导致运行时实际事务在目标对象中调用目标对象的另一种方法。另外,必须完全初始化代理以提供预期的行为,因此您不应在初始化代码(即@PostConstruct)中依赖此功能。

如果希望自调用也与事务一起包装,请考虑使用 AspectJ 模式(请参见下表中的 mode 属性)。在这种情况下,首先将没有代理。相反,将对目标类进行编织(即,将修改其字节码),以将@Transactional转换为任何方法上的运行时行为。

表 17.2Comments 驱动的 Transaction 设置

XML AttributeAnnotation AttributeDefaultDescription
transaction-managerN/A(请参阅TransactionManagementConfigurer javadocs)transactionManager要使用的事务 Management 器的名称。如上例所示,仅当事务 Management 器的名称不是transactionManager时才需要。
modemodeproxy默认模式“代理”使用 Spring 的 AOP 框架处理要 Comments 的 Bean(遵循如上所述的代理语义,仅适用于通过代理传入的方法调用)。替代模式“ aspectj”改为将受影响的类与 Spring 的 AspectJ 事务方面进行编织,修改目标类字节码以应用于任何类型的方法调用。 AspectJ 编织需要在 Classpath 中使用 spring-aspects.jar 并启用加载时编织(或编译时编织)。 (有关如何设置加载时编织的详细信息,请参见称为“ Spring 配置”的部分。)
proxy-target-classproxyTargetClassfalse仅适用于代理模式。控制为使用@TransactionalCommentsComments 的类创建哪种类型的事务代理。如果proxy-target-class属性设置为true,则将创建基于类的代理。如果proxy-target-classfalse或省略了属性,那么将创建基于标准 JDK 接口的代理。 (有关不同代理类型的详细检查,请参见第 11.6 节“代理机制”。)
orderorderOrdered.LOWEST_PRECEDENCE定义应用于带有@TransactionalComments 的 bean 的事务通知的 Sequences。 (有关与 AOP 建议的排序有关的规则的更多信息,请参见“建议 Order”部分。)没有指定的排序意味着 AOP 子系统确定建议的 Sequences。

Note

处理@Transactional注解的默认建议模式是“代理”,它仅允许通过代理进行呼叫拦截。同一类中的本地调用无法以这种方式被拦截。对于更高级的拦截模式,请考虑结合编译/加载时编织切换到“ aspectj”模式。

Note

proxy-target-class属性控制为使用@TransactionalComments 标注的类创建哪种类型的事务代理。如果proxy-target-class设置为true,则会创建基于类的代理。如果proxy-target-classfalse或省略了属性,则将创建基于标准 JDK 接口的代理。 (有关不同代理类型的讨论,请参见第 11.6 节“代理机制”。)

Note

@EnableTransactionManagement<tx:annotation-driven/>仅在定义它们的相同应用程序上下文中在 Bean 上查找@Transactional。这意味着,如果将 Comments 驱动的配置放在DispatcherServletWebApplicationContext中,则仅检查控制器中的@Transactional Bean,而不检查服务。有关更多信息,请参见第 22.2 节“ DispatcherServlet”

在评估方法的事务设置时,最派生的位置优先。在下面的示例中,使用只读事务的设置在类级别 CommentsDefaultFooService类,但是同一类中updateFoo(Foo)方法上的@TransactionalComments 优先于在类级别定义的事务设置。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

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

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

@Transactional settings

@TransactionalComments 是元数据,用于指定接口,类或方法必须具有事务性语义;例如,“ 在调用此方法时启动一个全新的只读事务,从而暂停任何现有事务”。默认的@Transactional设置如下:

  • 传播设置为PROPAGATION_REQUIRED.

  • 隔离级别为ISOLATION_DEFAULT.

  • 事务是读/写。

  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则默认为无。

  • 任何RuntimeException都会触发回滚,而所有选中的Exception都不会触发。

这些默认设置可以更改。下表总结了@Transactional注解的各种属性:

表 17.3 @Transaction 设置

PropertyTypeDescription
valueString可选的限定符,指定要使用的事务 Management 器。
propagation枚举:Propagation可选的传播设置。
isolation枚举:Isolation可选的隔离级别。仅适用于 REQUIRED 或 REQUIRES_NEW 传播。
timeoutint(以秒为单位的粒度)可选的事务超时。仅适用于 REQUIRED 或 REQUIRES_NEW 传播。
readOnlyboolean读/写与只读事务。仅适用于 REQUIRED 或 REQUIRES_NEW。
rollbackForClass个对象的数组,必须从Throwable.派生“必须”引起回滚的异常类的可选数组。
rollbackForClassName类名数组。类必须来自Throwable.“必须”引起回滚的异常类名称的可选数组。
noRollbackForClass个对象的数组,必须从Throwable.派生绝不能引起回滚的异常类的可选数组。
noRollbackForClassNameString类名称的数组,必须从Throwable.派生绝不能引起回滚的异常类名称的可选数组。

当前,您无法对事务名称进行显式控制,其中“名称”表示将在事务监视器(如果适用)(例如,WebLogic 的事务监视器)和日志输出中显示的事务名称。对于声明性事务,事务名称始终是完全合格的类名称“”。Transaction 建议类的方法名称。例如,如果BusinessService类的handlePayment(..)方法启动了事务,则事务的名称将为:com.foo.BusinessService.handlePayment

具有@Transactional 的多个事务 Management 器

大多数 Spring 应用程序仅需要一个事务 Management 器,但是在某些情况下,您可能需要在一个应用程序中使用多个独立的事务 Management 器。 @Transactional注解的 value 属性可用于可选地指定要使用的PlatformTransactionManager的标识。这可以是 bean 名称,也可以是事务 Management 器 bean 的限定符值。例如,使用限定符表示法,以下 Java 代码

public class TransactionalService {

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

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

可以在应用程序上下文中与以下事务 Management 器 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上的这两种方法将在不同的 TransactionManagement 器下运行,并以“订单”和“帐户”限定词区分。如果未找到特别限定的 PlatformTransactionManager bean,则仍将使用默认的<tx:annotation-driven>目标 bean 名称transactionManager

自定义快捷方式 Comments

如果发现您在许多不同的方法上重复使用@Transactional的相同属性,则Spring 的元 Comments 支持允许您为特定用例定义自定义快捷方式 Comments。例如,定义以下 Comments

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

使我们可以将上一节中的示例编写为

public class TransactionalService {

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

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

在这里,我们使用了语法来定义事务 Management 器限定符,但是还可以包括传播行为,回滚规则,超时等。

17.5.7Transaction 传播

本节描述了 Spring 中事务传播的一些语义。请注意,本节不是适当地介绍事务传播的;而是详细介绍了有关 Spring 中事务传播的一些语义。

在 SpringManagement 的事务中,请注意物理逻辑事务之间的差异,以及传播设置如何应用于该差异。

Required

需要 TX 道具

PROPAGATION_REQUIRED

当传播设置为PROPAGATION_REQUIRED时,将为应用该设置的每种方法创建一个逻辑事务作用域。每个这样的逻辑事务作用域可以单独确定仅回滚状态,而外部事务作用域在逻辑上独立于内部事务作用域。当然,在标准PROPAGATION_REQUIRED行为的情况下,所有这些范围都将 Map 到同一物理事务。因此,内部事务范围中设置的仅回滚标记确实会影响外部事务实际提交的机会(正如您期望的那样)。

但是,在内部事务范围设置仅回滚标记的情况下,外部事务尚未决定回滚本身,因此回滚(由内部事务范围默默触发)是意外的。此时将抛出一个对应的UnexpectedRollbackException。这是“预期的行为”,因此,事务的调用者永远不会被误认为是在确实未执行提交的情况下进行的。因此,如果内部事务(外部调用者不知道)内部将事务无提示地标记为仅回滚,则外部调用者仍会调用 commit。外部呼叫者需要接收UnexpectedRollbackException来明确指示已执行回滚。

RequiresNew

运行道具需要新的

PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRED相反,PROPAGATION_REQUIRES_NEW始终对每个受影响的事务范围使用独立物理事务,而从不参与外部范围的现有事务。在这种安排中,基础资源事务是不同的,因此可以独立地提交或回滚,而外部事务不受内部事务回滚状态的影响,内部事务的锁在完成后立即释放。这样的独立内部事务也可以声明其自己的隔离级别,超时和只读设置,而从不继承外部事务的 Feature。

Nested

PROPAGATION_NESTED使用单个物理事务,该事务具有多个可以回滚到的保存点。这样的部分回滚允许内部事务作用域触发范围的回滚*,尽管某些操作已被回滚,但外部事务仍能够 continue 物理事务。此设置通常 Map 到 JDBC 保存点,因此仅适用于 JDBC 资源事务。参见 Spring 的DataSourceTransactionManager

17.5.8 为 Transaction 操作提供建议

假设您要同时执行事务和*一些基本的分析建议。您如何在<tx:annotation-driven/>的情况下实现此目的?

调用updateFoo(Foo)方法时,您想要查看以下操作:

  • 已配置的性能分析方面启动。

  • Transaction 建议执行。

  • 建议对象上的方法执行。

  • Transaction commits.

  • 分析方面报告整个事务方法调用的确切持续时间。

Note

本章不涉及任何详细的 AOP 解释(除非它适用于事务)。有关以下 AOP 配置和一般 AOP 的详细介绍,请参见第 11 章,使用 Spring 进行面向方面的编程

这是上面讨论的简单配置方面的代码。建议的排序是通过Ordered界面控制的。有关建议 Order 的完整详细信息,请参见“建议 Order”部分。 。

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>

上面配置的结果是一个fooService bean,它以所需 Sequences*对其应用了概要分析和事务方面。您可以类似的方式配置任意数量的其他方面。

下面的示例实现与上述相同的设置,但是使用纯 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>

以上配置的结果将是一个fooService bean,该 bean 依次(按该 Sequences)应用了概要分析和事务方面。如果您希望性能分析建议在进来的事务建议之后执行,而在 Export 事务处理的建议之前之前*执行,那么您只需交换性能分析方面 Bean 的order属性的值,使其大于 Transaction 建议的订单价值。

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

17.5.9 在 AspectJ 中使用@Transactional

也可以通过 AspectJ 方面在 Spring 容器之外使用 Spring Framework 的@Transactional支持。为此,您首先需要使用@TransactionalComments 对类(以及可选的类的方法)进行 Comments,然后使用spring-aspects.jar文件中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect链接(编织)应用程序。该方面还必须配置有事务 Management 器。当然,您可以使用 Spring Framework 的 IoC 容器来进行依赖注入方面。配置事务 Management 方面的最简单方法是使用<tx:annotation-driven/>元素并将mode属性指定为aspectj,如第 17.5.6 节“使用@Transactional”中所述。因为我们在这里专注于在 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);

Note

使用此方面时,必须 Comments* implementation *类(和/或该类中的方法),而不是 Comments 该类所实现的接口(如果有)。 AspectJ 遵循 Java 的规则,即“不继承”接口上的 Comments。

类上的@TransactionalComments 指定用于执行该类中任何公共方法的默认事务语义。

类中方法上的@TransactionalComments 将覆盖类 Comments(如果存在)给出的默认事务语义。无论可见性如何,都可以 Comments 任何方法。

要使用AnnotationTransactionAspect编织应用程序,您必须使用 AspectJ 构建应用程序(请参见AspectJ 开发指南)或使用加载时编织。有关使用 AspectJ 进行加载时编织的讨论,请参见第 11.8.4 节“在 Spring 框架中使用 AspectJ 进行加载时编织”

17.6 程序化 TransactionManagement

Spring 框架提供了两种程序化事务 Management 方式:

  • 使用TransactionTemplate

  • 直接使用PlatformTransactionManager实现。

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

17.6.1 使用 TransactionTemplate

TransactionTemplate采用与其他 Spring 模板(例如JdbcTemplate)相同的方法。它使用一种回调方法,使应用程序代码不必进行样板获取和释放事务性资源,从而得到意向驱动的代码,因为所编写的代码仅专注于开发人员想要做的事情。

Note

正如您将在以下示例中看到的那样,使用TransactionTemplate绝对可以使您与 Spring 的事务基础结构和 API 耦合。程序化事务 Management 是否适合您的开发需求,这是您必须自己做的决定。

必须在事务上下文中执行并显式使用TransactionTemplate的应用程序代码如下所示。您作为应用程序开发人员,编写一个TransactionCallback实现(通常表示为匿名内部类),其中包含您需要在事务上下文中执行的代码。然后,您将自定义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();
            }
        });
    }
}

如果没有返回值,请使用方便的TransactionCallbackWithoutResult类和匿名类,如下所示:

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

回调中的代码可以通过调用提供的TransactionStatus对象上的setRollbackOnly()方法来回滚事务:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

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

指定 Transaction 设置

您可以通过编程或配置方式在TransactionTemplate上指定事务设置,例如传播模式,隔离级别,超时等。默认情况下,TransactionTemplate个实例具有默认 Transaction 设置。以下示例显示了针对特定TransactionTemplate:的 Transaction 设置的编程自定义

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

以下示例使用 Spring XML 配置定义了具有一些自定义事务设置的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类的实例是线程安全的,因为这些实例不维护任何对话状态。 TransactionTemplate个实例确实保持配置状态,因此尽管许多类可以共享TransactionTemplate的单个实例,但是如果一个类需要使用具有不同设置(例如,不同的隔离级别)的TransactionTemplate,则您需要创建两个不同的TransactionTemplate实例。

17.6.2 使用 PlatformTransactionManager

您也可以直接使用org.springframework.transaction.PlatformTransactionManager来 Management 您的 Transaction。只需通过 bean 引用将您正在使用的PlatformTransactionManager的实现传递给 bean。然后,使用TransactionDefinitionTransactionStatus对象可以启动事务,回滚并提交。

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 在编程式和声明式事务 Management 之间进行选择

仅当您执行少量 Transaction 操作时,程序 TransactionManagement 通常是一个好主意。例如,如果您的 Web 应用程序仅要求对某些更新操作进行事务处理,则可能不希望使用 Spring 或任何其他技术来设置事务代理。在这种情况下,使用TransactionTemplate 可能是一个好方法。能够显式设置事务名称也是只能使用程序化方法进行事务 Management 的功能。

另一方面,如果您的应用程序具有大量事务操作,则声明式事务 Management 通常是值得的。它使事务 Management 脱离业务逻辑,并且不难配置。当使用 Spring 框架而不是 EJB CMT 时,声明式事务 Management 的配置成本大大降低了。

17.8Transaction 绑定事件

从 Spring 4.2 开始,事件的侦听器可以绑定到事务的某个阶段。典型的示例是在事务成功完成时处理事件:当当前事务的结果实际上对侦听器很重要时,这使事件可以更灵活地使用。

通过@EventListener注解完成常规事件监听器的注册。如果需要将其绑定到事务,请使用@TransactionalEventListener。这样做时,默认情况下,侦听器将绑定到事务的提交阶段。

让我们以一个例子来说明这个概念。假设组件发布了一个订单创建事件,并且我们想要定义一个侦听器,该侦听器仅在发布该事件的事务成功提交后才处理该事件:

@Component
public class MyComponent {

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

TransactionalEventListenerComments 公开了phase属性,该属性使我们可以自定义侦听器应绑定到的事务的哪个阶段。有效阶段是BEFORE_COMMITAFTER_COMMIT(默认),AFTER_ROLLBACKAFTER_COMPLETION,这些阶段汇总事务完成(无论是提交还是回滚)。

如果没有事务在运行,则由于我们无法遵守所需的语义,因此根本不会调用侦听器。但是,可以通过将 Comments 的fallbackExecution属性设置为true来覆盖该行为。

17.9 特定于应用程序服务器的集成

Spring 的事务抽象通常与应用服务器无关。此外,Spring 的JtaTransactionManager类(可以选择对 JTA UserTransactionTransactionManager对象执行 JNDI 查找)可以自动检测后一个对象的位置,具体取决于应用程序服务器。可以使用 JTA TransactionManager来增强事务语义,特别是支持事务挂起。有关详细信息,请参见JtaTransactionManager javadocs。

Spring 的JtaTransactionManager是在 Java EE 应用程序服务器上运行的标准选择,并且已知可以在所有普通服务器上运行。诸如事务挂起之类的高级功能也可以在许多服务器上运行,包括 GlassFish,JBoss 和 Geronimo,而无需任何特殊配置。但是,为了完全支持事务暂停和进一步的高级集成,Spring 附带了适用于 WebLogic Server 和 WebSphere 的特殊适配器。这些适配器将在以下各节中讨论。

*对于标准场景,包括 WebLogic Server 和 WebSphere,请考虑使用方便的<tx:jta-transaction-manager/>配置元素.*配置后,此元素会自动检测基础服务器并选择适用于该平台的最佳事务 Management 器。这意味着您不必显式配置服务器特定的适配器类(如以下各节所述)。而是会自动选择它们,并以标准JtaTransactionManager作为默认后备。

17.9.1 IBM WebSphere

在 WebSphere 6.1.0.9 和更高版本上,推荐使用的 Spring JTA 事务 Management 器是WebSphereUowTransactionManager。这个特殊的适配器利用了 IBM 的UOWManager API,该 API 在 WebSphere Application Server 6.1.0.9 和更高版本中可用。使用此适配器,IBM 正式支持 Spring 驱动的事务挂起(由PROPAGATION_REQUIRES_NEW启动的挂起/恢复)。

17.9.2 Oracle WebLogic Server

在 WebLogic Server 9.0 或更高版本上,通常将使用WebLogicJtaTransactionManager而不是 stock JtaTransactionManager类。常规JtaTransactionManager的特定于 WebLogic 的特殊子类在标准 JTA 语义之外,支持 WebLogicManagement 的事务环境中 Spring 事务定义的全部功能:功能包括事务名称,每个事务隔离级别以及在所有情况下正确恢复事务。

17.10 常见问题的解决方案

17.10.1 为特定的数据源使用了错误的事务 Management 器

根据您选择的 Transaction 技术和要求,使用正确 PlatformTransactionManager实现。如果使用得当,Spring 框架仅提供了直接且可移植的抽象。如果您正在使用全局事务,那么您必须org.springframework.transaction.jta.JtaTransactionManager类(或其中的应用服务器特定的子类)用于所有事务操作。否则,事务基础结构将尝试对诸如容器DataSource实例之类的资源执行本地事务。这样的本地事务是没有意义的,好的应用服务器会将它们视为错误。

17.11 更多资源

有关 Spring 框架的事务支持的更多信息:

  • 带有和不带有 XA 的 Spring 中的分布式事务是 JavaWorld 的演示文稿,其中 Spring 的 David Syer 指导您完成在 Spring 应用程序中进行分布式事务处理的七个模式,其中三个具有 XA 模式,四个具有 XA 模式。

  • Java 事务设计策略InfoQ提供的一本书,详细介绍了 Java 事务。它还包括有关如何通过 Spring Framework 和 EJB3 配置和使用事务的并行示例。