Spring 事务管理

目录

1. 事务管理

1.1. Spring框架的事务支持模型的优势

1.1.1. 全局事务

1.1.2. 本地事务

1.1.3. Spring框架的一致化编程模型

1.2. 了解Spring框架的事务抽象(Transaction Abstraction)

1.2.1. Hibernate 事务设置

1.3. 用事务同步资源

1.3.1. 高级同步方式

1.3.2. 低级同步方式

1.3.3.TransactionAwareDataSourceProxy

1.4. 声明式事务管理

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

1.4.2. 声明式事务实现的例子

1.4.3. 回滚声明式事务

1.4.4. 为不同的ban配置不同的事务性语义

1.4.5. 设置

1.4.6. 使用@Transactional

@Transactional设置

使用@Transactional的多个事务管理器

自定义“组成”注解

1.4.7. 事务传播(Propagation)

理解PROPAGATION_REQUIRED

理解PROPAGATION_NESTED

1.4.8. advice 事务操作

1.4.9. 在 AspectJ 中使用@Transactional

1.5. 编程式事务管理

1.5.1. 使用TransactionTemplate

指定事务设置

1.5.2. 使用TransactionalOperator

取消信号

指定事务设置

1.5.3. 使用TransactionManager

使用PlatformTransactionManager

使用ReactiveTransactionManager

1.6. 在编程式和声明式事务管理之间做出选择

1.7. 事务绑定的事件

1.8. 特定的应用服务器整合

1.8.1. IBM WebSphere

1.8.2. Oracle WebLogic Server


1. 事务管理

全面的事务支持是使用Spring框架的最有说服力的理由之一。Spring框架为事务管理提供了一个一致的抽象,提供了以下好处:

  • 在不同的事务 API之间有一个一致的编程模型,如Java Transaction API(JTA)、JDBC、Hibernate和Java Persistence API(JPA)。
  • 支持 声明式事务管理。
  • 与复杂的事务API(如JTA)相比,一个更简单的 编程式 事务管理API。
  • 与Spring的数据访问抽象的出色集成。

下面的章节描述了Spring框架的事务特性和技术:

  • Spring框架的事务支持模型的优势描述了为什么你会使用Spring框架的事务抽象,而不是EJB容器管理的事务(CMT)或选择通过专有API(如Hibernate)驱动本地事务。
  • 了解Spring框架事务抽象 概述了核心类,并描述了如何配置和从各种来源获取 DataSource 实例。
  • 用事务同步资源 描述了应用程序代码如何确保资源被正确创建、重复使用和清理。
  • 声明式事务管理 描述了对声明式事务管理的支持。
  • 程序化事务管理 涵盖了对程序化(即明确编码的)事务管理的支持。
  • 事务绑定事件 描述了你如何在一个事务中使用应用程序事件。

该章还包括对最佳实践、应用服务器集成 和 常见问题解决方案 的讨论。

1.1. Spring框架的事务支持模型的优势

传统上,EE应用程序开发人员在事务管理方面有两种选择:全局或本地事务,这两种事务都有深刻的局限性。在接下来的两节中,我们将回顾全局和局部事务管理,然后讨论Spring框架的事务管理支持如何解决全局和局部事务模型的限制。

1.1.1. 全局事务

全局事务让你与多个事务性资源一起工作,通常是关系型数据库和消息队列。应用服务器通过JTA管理全局事务,这是一个繁琐的API(部分原因是其异常模型)。此外,JTA的 UserTransaction 通常需要从JNDI获取,这意味着你也需要使用JNDI才能使用JTA。全局事务的使用限制了任何潜在的应用程序代码的重用,因为JTA通常只在应用程序服务器环境中可用。

以前,使用全局事务的首选方式是通过EJB CMT(容器管理事务)。CMT是声明式事务管理的一种形式(区别于程序化事务管理)。EJB CMT消除了对事务相关的JNDI查询的需要,尽管EJB的使用本身就需要使用JNDI。它消除了大部分但不是全部编写Java代码来控制事务的需要。重要的缺点是,CMT与JTA和应用服务器环境相联系。而且,只有当人们选择在EJB中实现业务逻辑(或至少在事务性的 EJB facade 后面)时,它才可用。EJB的缺点是如此之多,以至于这不是一个有吸引力的提议,特别是在面对声明式事务管理的引人注目的替代方案时。

1.1.2. 本地事务

本地事务是特定资源的,例如与JDBC连接相关的事务。本地事务可能更容易使用,但有一个明显的缺点: 它们不能跨多个事务资源工作。例如,通过使用JDBC连接管理事务的代码不能在全局JTA事务中运行。因为应用服务器没有参与事务管理,它不能帮助确保跨多个资源的正确性。(值得注意的是,大多数应用程序都使用单一的事务资源)。另一个缺点是,本地事务对编程模型有侵入性。

1.1.3. Spring框架的一致化编程模型

Spring解决了全局和局部事务的弊端。它让应用开发者在任何环境下都能使用一个一致的编程模型。你只需写一次代码,它就可以在不同的环境中受益于不同的事务管理策略。Spring框架同时提供声明式和编程式事务管理。大多数用户更喜欢声明式事务管理,我们在大多数情况下都推荐这样做。

通过编程式事务管理,开发人员使用Spring框架的事务抽象,它可以在任何底层事务基础设施上运行。通过首选的声明式模型,开发人员通常很少或不写与事务管理有关的代码,因此,不依赖于Spring框架的事务API或任何其他事务API。

你需要一个用于事务管理的应用服务器吗?

Spring框架的事务管理支持改变了传统的规则,即企业Java应用何时需要应用服务器。

特别是,你不需要一个纯粹的应用服务器来通过EJB进行声明式事务处理。事实上,即使你的应用服务器有强大的JTA功能,你也可能决定Spring框架的声明式事务比EJB CMT提供更多的功能和更多的编程模型。

通常情况下,只有当你的应用程序需要跨多个资源处理事务时,你才需要应用服务器的JTA能力,这对许多应用程序来说不是一个要求。许多高端应用使用一个单一的、高度可扩展的数据库(如Oracle RAC)来代替。独立的事务管理器(如 Atomikos Transactions)是其他选择。当然,你可能需要其他应用服务器的能力,如Java消息服务(JMS)和 Jakarta EE Connector 架构(JCA)。

Spring框架让你可以选择何时将你的应用扩展到一个满载的应用服务器。过去,使用EJB CMT或JTA的唯一选择是编写本地事务的代码(比如JDBC连接上的事务),如果你需要这些代码在全局的、容器管理的事务中运行,就要面临巨大的返工。有了Spring框架,只有你的配置文件中的一些bean定义需要改变(而不是你的代码)。

1.2. 了解Spring框架的事务抽象(Transaction Abstraction)

Spring事务抽象的关键是事务策略的概念。事务策略是由 TransactionManager 定义的,特别是用于强制事务管理的 org.springframework.transaction.PlatformTransactionManager 接口和用于响应式事务管理的 org.springframework.transaction.ReactiveTransactionManager 接口。下面的列表显示了 PlatformTransactionManager API的定义:

public interface PlatformTransactionManager extends TransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这主要是一个服务提供者接口(SPI),尽管你可以从你的应用代码中以 编程方式 使用它。因为 PlatformTransactionManager 是一个接口,它可以很容易地被 mock 或在必要时被 stub。它不与查找策略相联系,例如 JNDI。PlatformTransactionManager 的实现与 Spring Framework IoC容器中的其他对象(或Bean)一样被定义。仅仅是这个好处就使Spring框架的事务成为一个值得一试的抽象,即使是在你使用JTA的时候。你可以比直接使用JTA更容易地测试事务性代码。

同样,为了与Spring的理念保持一致,可以由 PlatformTransactionManager 接口的任何方法抛出的 TransactionException 是未检查(非运行时)的(也就是说,它继承了 java.lang.RuntimeException 类)。事务基础设施的失败几乎无一例外地是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复过来,应用开发者仍然可以选择 catch 和处理 TransactionException。突出的一点是,开发者并不被迫这样做。

getTransaction(..) 方法返回一个 TransactionStatus 对象,这取决于 TransactionDefinition 参数。返回的 TransactionStatus 可能代表一个新的事务,也可能代表一个现有的事务,如果在当前调用栈中存在一个匹配的事务。后者的含义是,与 Jakarta EE的事务上下文一样,TransactionStatus 与执行线程相关。

从Spring Framework 5.2开始,Spring也为利用响应式类型或 Kotlin Coroutines 的响应式应用提供了一个事务管理抽象。下面的列表显示了 org.springframework.transaction.ReactiveTransactionManager 所定义的事务策略:

public interface ReactiveTransactionManager extends TransactionManager {

    Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;

    Mono<Void> commit(ReactiveTransaction status) throws TransactionException;

    Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
}

响应式事务管理器主要是一个服务提供者接口(SPI),尽管你可以从你的应用程序代码中以 编程方式 使用它。因为 ReactiveTransactionManager 是一个接口,它可以根据需要很容易地被 mock 或 stub。

TransactionDefinition 接口规定了:

  • Propagation: 通常情况下,一个事务范围内的所有代码都在该事务中运行。然而,如果一个事务性方法在一个已经存在的事务上下文中被运行,你可以指定其行为。例如,代码可以继续在现有的事务中运行(常见的情况),或者暂停现有的事务并创建一个新的事务。Spring提供了所有从EJB CMT中熟悉的事务传播选项。要了解Spring中事务传播的语义,请看 事务传播(Propagation)。
  • Isolation: 这个事务与其他事务的隔离级别。例如,这个事务能否看到其他事务的未提交的写操作?
  • Timeout: 这个事务会保持多久直到由基础事务基础设施自动超时并回滚。
  • Read-only status(只读状态): 当你的代码读取但不修改数据时,你可以使用一个只读事务。只读事务在某些情况下是一种有用的优化,例如当你使用Hibernate时。

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

TransactionStatus 接口为事务代码提供了一种简单的方法来控制事务执行和查询事务状态。这些概念应该是熟悉的,因为它们是所有事务API所共有的。下面的列表显示了 TransactionStatus 接口:

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {

    @Override
    boolean isNewTransaction();

    boolean hasSavepoint();

    @Override
    void setRollbackOnly();

    @Override
    boolean isRollbackOnly();

    void flush();

    @Override
    boolean isCompleted();
}

无论你在Spring中选择声明式还是编程式事务管理,定义正确的 TransactionManager 实现是绝对必要的。你通常通过依赖注入来定义这个实现。

TransactionManager 的实现通常需要对其工作环境的了解: JDBC、JTA、Hibernate,等等。下面的例子显示了你如何定义一个本地的 PlatformTransactionManager 实现(在这种情况下,用普通的JDBC。)

你可以通过创建一个与下面类似的 bean 来定义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>

如果你在Jakarta EE容器中使用JTA,那么你就使用通过JNDI获得的容器 DataSource,并结合Spring的 JtaTransactionManager。下面的例子显示了JTA和JNDI的查询(lookup)版本是什么样子的:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://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 Bean的定义使用了jee命名空间的 <jndi-lookup/> 标签。欲了解更多信息,请参见 JEE Schema。

如果你使用JTA,你的事务管理器定义应该看起来是一样的,无论你使用什么数据访问技术,无论是JDBC、Hibernate JPA,还是其他任何支持的技术。这是由于JTA事务是全局事务,它可以征集任何事务性资源。 

在所有Spring事务设置中,应用程序代码不需要改变。你可以仅仅通过改变配置来改变事务的管理方式,即使这种改变意味着从本地事务转移到全局事务,或者反之亦然。

1.2.1. Hibernate 事务设置

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

DataSource Bean 的定义与之前显示的本地JDBC例子相似,因此,在下面的例子中没有显示。

如果 DataSource(由任何非JTA事务管理器使用)通过JNDI查找并由Jakarta EE容器管理,它应该是非事务性的,因为Spring框架(而不是Jakarta EE容器)管理着事务。 

本例中的 txManager Bean 是 HibernateTransactionManager 类型的。与 DataSourceTransactionManager 需要对 DataSource 的引用一样, HibernateTransactionManager 需要对 SessionFactory 的引用。下面的例子声明了 sessionFactory 和 txManager bean:

<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和Jakarta EE容器管理的JTA事务,你应该使用与前面JDBC的JTA例子中相同的 JtaTransactionManager,如下例所示。另外,建议让Hibernate通过它的事务协调器(transaction coordinator),可能还包括它的连接(connection)释放模式配置来了解JTA:

<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}
            hibernate.transaction.coordinator_class=jta
            hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
        </value>
    </property>
</bean>

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

或者,你可以将 JtaTransactionManager 传递给你的 LocalSessionFactoryBean,以执行相同的默认值:

<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>
    <property name="jtaTransactionManager" ref="txManager"/>
</bean>

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

1.3. 用事务同步资源

如何创建不同的事务管理器,以及如何将它们与需要同步到事务的相关资源(例如 DataSourceTransactionManager 到JDBC DataSource,HibernateTransactionManager 到 Hibernate SessionFactory,等等)联系起来,现在应该清楚了。本节描述了应用程序代码(直接或间接地通过使用持久化API,如JDBC、Hibernate或JPA)如何确保这些资源被正确地创建、重复使用和清理。本节还讨论了如何通过相关的 TransactionManager 触发事务同步(可选)。

1.3.1. 高级同步方式

首选的方法是使用Spring最高级别的基于模板的持久化集成API,或者使用本地ORM API与事务感知(transaction-aware)工厂bean或代理来管理本地资源工厂。这些事务感知解决方案在内部处理资源的创建和重用、清理、资源的可选事务同步,以及异常映射。因此,用户数据访问代码不需要处理这些任务,而是可以纯粹地专注于非模板持久化逻辑。一般来说,你可以使用本地ORM API,或者通过使用 JdbcTemplate 为JDBC访问采取模板方法。这些解决方案将在本参考文档的后续章节中详细介绍。

1.3.2. 低级同步方式

诸如 DataSourceUtils(用于JDBC)、EntityManagerFactoryUtils(用于JPA)、 SessionFactoryUtils(用于Hibernate)等类存在于较低层次。当你想让应用程序代码直接处理本地持久化API的资源类型时,你可以使用这些类来确保获得正确的Spring框架管理的实例,事务(可选择)同步,以及在此过程中发生的异常被正确地映射到一致的API。

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

Connection conn = DataSourceUtils.getConnection(dataSource);

如果一个现有的事务已经有一个与之同步(链接)的连接,该实例将被返回。否则,该方法的调用会触发一个新的连接的创建,该连接(可选择)与任何现有的事务同步,并可在同一事务中进行后续重用。如前所述,任何 SQLException 都被包裹在Spring框架的 CannotGetJdbcConnectionException 中,这是Spring框架未检查的 DataAccessException 类型的层次结构之一。这种方法为你提供了比从 SQLException 中轻易获得的更多信息,并确保了跨数据库甚至不同持久化技术的可移植性。

这种方法在没有Spring事务管理的情况下也可以使用(事务同步是可选的),所以无论你是否使用Spring的事务管理,都可以使用它。

当然,一旦你使用了Spring的JDBC支持、JPA支持或Hibernate支持,一般来说,你最好不要使用 DataSourceUtils 或其他辅助类,因为你通过Spring的抽象工作要比直接使用相关的API更快乐。例如,如果你使用Spring JdbcTemplate 或 jdbc.object 包来简化你对JDBC的使用,正确的连接(connection)检索会在幕后发生,你不需要编写任何特殊的代码。

1.3.3.TransactionAwareDataSourceProxy

最底层的是 TransactionAwareDataSourceProxy 类。这是一个目标(target) DataSource 的代理,它包装了 DataSource 以增加对Spring管理的事务的感知。在这方面,它类似于Jakarta EE服务器提供的事务型JNDI DataSource。

你几乎不应该需要或想要使用这个类,除非现有的代码必须被调用并传递一个标准的JDBC DataSource 接口实现。在这种情况下,这段代码有可能是可用的,但却参与了Spring管理的事务。你可以通过使用前面提到的高级抽象来编写你的新代码。

1.4. 声明式事务管理

大多数Spring框架用户选择声明式事务管理。这个选项对应用程序代码的影响最小,因此与非侵入性的轻量级容器的理想最为一致。 

Spring框架的声明式事务管理是通过Spring面向切面编程(AOP)实现的。然而,由于事务方面的代码是随Spring框架的发布而来,并且可以以模板的方式使用,所以一般不需要理解AOP的概念来有效地使用这些代码。

Spring框架的声明式事务管理与EJB CMT类似,你可以指定事务行为(或不指定),直至单个方法级别。如果有必要,你可以在事务上下文中进行 setRollbackOnly() 调用。这两种类型的事务管理的区别在于:

  • 不像EJB CMT与JTA绑定,Spring框架的声明式事务管理可以在任何环境下工作。通过调整配置文件,它可以与JTA事务或使用JDBC、JPA或Hibernate的本地事务一起工作。
  • 你可以将Spring框架的声明式事务管理应用于任何类,而不仅仅是EJB这样的特殊类。
  • Spring框架提供了声明式的 回滚规则,这是一个没有EJB对应的功能。对回滚规则的编程式和声明式支持都有提供。
  • Spring框架允许你通过使用AOP来定制事务行为。例如,你可以在事务回滚的情况下插入自定义行为。你还可以添加任意的advice,以及事务性advice。通过EJB CMT,你不能影响容器的事务管理,除非使用 setRollbackOnly()。
  • Spring框架不支持跨远程调用的事务上下文的传播,就像高端应用服务器那样。如果你需要这个功能,我们建议你使用EJB。然而,在使用这种功能之前,请仔细考虑,因为,通常情况下,人们不希望事务跨越远程调用。

回滚规则的概念很重要。它们让你指定哪些异常(和 throwable)应该导致自动回滚。你可以在配置中声明性地指定,而不是在Java代码中。因此,尽管你仍然可以在 TransactionStatus 对象上调用 setRollbackOnly() 来回滚当前事务,但最常见的是你可以指定一个规则,即 MyApplicationException 必须总是导致回滚。这个选项的显著优势是,业务对象不依赖于事务基础设施。例如,它们通常不需要导入Spring事务API或其他Spring API。

尽管EJB容器的默认行为会在系统异常(通常是运行时异常)时自动回滚事务,但EJB CMT不会在应用程序异常(即除 java.rmi.RemoteException 以外的被检查的异常)时自动回滚事务。虽然Spring声明式事务管理的默认行为遵循了EJB的惯例(只在未检查的异常上自动回滚),但自定义这种行为往往是有用的。

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

仅仅告诉你用 @Transactional 注解来注解你的类,将 @EnableTransactionManagement 添加到你的配置中,并期望你能理解这一切是如何运作的,这是不够的。为了提供更深入的理解,本节将在事务相关问题的背景下解释Spring框架的声明式事务基础设施的内部运作。

关于Spring框架的声明性事务支持,最重要的概念是这种支持是通过 AOP代理 实现的,而且事务advice是由元数据(目前是基于XML或注解)驱动的。AOP与事务元数据的结合产生了一个AOP代理,它使用 TransactionInterceptor 与适当的 TransactionManager 实现来驱动围绕方法调用的事务。

Spring Framework的 TransactionInterceptor 为命令式和响应式编程模型提供事务管理。拦截器通过检查方法的返回类型来检测所需的事务管理方式。返回响应式类型的方法,如 Publisher 或 Kotlin Flow(或其子类型),有资格进行响应式事务管理。所有其他的返回类型,包括 void,都使用命令式事务管理的代码路径。

事务管理的 “味道” 会影响到需要哪个事务管理器。强制性事务需要 PlatformTransactionManager,而响应性事务使用 ReactiveTransactionManager 实现。

@Transactional 通常与 PlatformTransactionManager 管理的线程绑定的事务一起工作,将一个事务暴露给当前执行线程内的所有数据访问操作。注意:这不会传播到方法内新启动的线程。

由 ReactiveTransactionManager 管理的响应式事务使用 Reactor 上下文而不是 thread-local 属性。因此,所有参与的数据访问操作都需要在同一 reactive pipeline 中的同一 Reactor 上下文中执行。

下图显示了在事务型代理上调用方法的概念性视图:

1.4.2. 声明式事务实现的例子

考虑一下下面的接口及其附带的实现。这个例子使用 Foo 和 Bar 类作为占位符,这样你就可以专注于事务的使用,而不用关注特定的 domain 模型。就本例而言,DefaultFooService 类在每个实现方法的主体中抛出 UnsupportedOperationException 实例这一事实是好的。这种行为让你看到事务被创建,然后回滚以响应 UnsupportedOperationException 实例。下面的列表显示了 FooService 的接口:

Java

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

}

下面的例子显示了前述接口的一个实现:

Java

package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}

假设 FooService 接口的前两个方法 getFoo(String) 和 getFoo(String, String) 必须在具有只读语义的事务上下文中运行,其他方法 insertFoo(Foo) 和 updateFoo(Foo) 必须在具有读写语义的事务下运行。下面几段将详细解释下面的配置:

<!-- 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 TransactionManager -->
    <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 属性被设置为将要驱动事务的 TransactionManager Bean的名称(在本例中,是 txManager Bean)。

如果你想接入的 TransactionManager 的 bean 名称是 transactionManager,你可以在事务 advice(<tx:advice/>)中省略 transaction-manager 属性。如果你想接入的 TransactionManager Bean 有任何其他名字,你必须明确地使用 transaction-manager 属性,就像前面的例子一样。

<aop:config/> 定义确保由 txAdvice Bean 定义的事务性advice在程序中的适当点运行。首先,你定义一个与 FooService 接口(fooServiceOperation)中定义的任何操作的执行相匹配的 pointcut。然后,你通过使用 advisor 将该 pointcut 与 txAdvice 联系起来。结果表明,在执行 fooServiceOperation 的时候,由 txAdvice 定义的 advice 被运行。

在 <aop:pointcut/> 元素中定义的表达式是一个 AspectJ pointcut 表达式。关于Spring中的 pointcut 表达式的更多细节,请参见 AOP部分。

一个常见的要求是使整个服务层成为事务性的。做到这一点的最好方法是改变 pointcut 表达式以匹配你的服务层中的任何操作。下面的例子展示了如何做到这一点: s

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

在前面的例子中,假设你的所有服务接口都定义在 x.y.service 包中。更多细节请参见 AOP部分。

现在我们已经分析了配置,你可能会问自己,"这些配置到底是做什么的?"

前面显示的配置被用来在从 fooService Bean定义中创建的对象周围创建一个事务性代理。该代理被配置为具有事务 advice,因此,当代理上调用适当的方法时,事务被启动、暂停、标记为只读,等等,取决于与该方法相关的事务配置。考虑一下下面的程序,它测试了前面所示的配置:

Java

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
        FooService fooService = ctx.getBean(FooService.class);
        fooService.insertFoo(new Foo());
    }
}

运行前述程序的输出应类似于以下内容(为了清晰起见,DefaultFooService 类的 insertFoo(..) 方法抛出的 UnsupportedOperationException 的 Log4J 输出和 stack trace 已被截断):

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

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

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

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

为了使用响应式事务管理,代码必须使用响应式类型。

Spring Framework 使用 ReactiveAdapterRegistry 来确定一个方法的返回类型是否是响应式的。

下面的列表显示了之前使用的 FooService 的修改版本,但这次代码使用了响应式类型:

Java

package x.y.service;

public interface FooService {

    Flux<Foo> getFoo(String fooName);

    Publisher<Foo> getFoo(String fooName, String barName);

    Mono<Void> insertFoo(Foo foo);

    Mono<Void> updateFoo(Foo foo);

}

下面的例子显示了前述接口的一个实现:

Java

package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Flux<Foo> getFoo(String fooName) {
        // ...
    }

    @Override
    public Publisher<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}

强制性和响应性事务管理在事务边界和事务属性的定义上有相同的语义。强制性和响应性事务的主要区别在于后者的延迟性。TransactionInterceptor 用事务操作符装饰返回的反应式类型,以开始和清理事务。因此,调用一个事务性的响应式方法将实际的事务管理推迟到激活响应式类型的处理的订阅类型。

响应式事务管理的另一个切面与数据转义有关,这是编程模型的一个自然结果。

当一个方法成功终止时,强制性事务的方法返回值将从事务性方法中返回,这样部分计算的结果就不会逃脱方法关闭。

响应式事务方法返回一个响应式包装类型,该类型代表一个计算序列以及一个开始和完成计算的承诺(promise)。

一个 Publisher 可以在事务进行的过程中发布数据,但不一定能完成。因此,依赖于整个事务成功完成的方法需要在调用代码中确保完成和缓冲结果。

1.4.3. 回滚声明式事务

上一节概述了如何在你的应用程序中以声明的方式为类(通常是服务层类)指定事务性设置的基本内容。这一节描述了如何在 XML 配置中以简单、声明性的方式控制事务的回滚。关于用 @Transactional 注解声明性地控制回滚语义的细节,请参见 @Transactional 设置。

向Spring框架的事务基础架构表明事务工作将被回滚的推荐方法是,从当前正在事务上下文中执行的代码中抛出一个异常。Spring框架的事务基础架构代码会在调用堆栈中捕获任何未处理的异常,并决定是否对事务进行回滚标记。

在其默认配置中,Spring框架的事务基础架构代码仅在运行时未检查的异常情况下标记事务进行回滚。也就是说,当抛出的异常是 RuntimeException 的一个实例或子类时。(默认情况下,Error 实例也会导致回滚)。

从 Spring Framework 5.2 开始,默认配置还提供了对 Vavr 的 Try 方法的支持,以在其返回 'Failure' 时触发事务回滚。这允许你使用 Try 来处理函数式错误,并在失败的情况下让事务自动回滚。关于 Vavr 的 Try 的更多信息,请参考 Vavr官方文档

下面是一个如何使用 Vavr 的 Try 与事务方法的例子:

Java

@Transactional
public Try<String> myTransactionalMethod() {
    // If myDataAccessOperation throws an exception, it will be caught by the
    // Try instance created with Try.of() and wrapped inside the Failure class
    // which can be checked using the isFailure() method on the Try instance.
    return Try.of(delegate::myDataAccessOperation);
}

在默认配置中,从事务性方法抛出的受检查的异常不会导致回滚。你可以通过指定回滚规则,准确地配置哪些 Exception 类型将事务标记为回滚,包括受检查的异常。

回滚规则

回滚规则决定当一个给定的异常被抛出时,事务是否应该被回滚,规则是基于异常类型或异常模式的。

回滚规则可以通过 rollback-for 和 no-rollback-for 属性在XML中配置,这允许规则被定义为模式。当使用 @Transactional 时,可以通过 rollbackFor/noRollbackFor 和 rollbackForClassName/noRollbackForClassName 属性来配置回滚规则,它们允许分别根据异常类型或模式来定义规则。

当一个回滚规则被定义为一个exception类型时,该类型将被用来与抛出的exception及其超类型相匹配,提供了类型安全并避免了使用模式时可能发生的任何意外匹配。例如,jakarta.servlet.ServletException.class 的值将只匹配 jakarta.servlet.ServletException 类型及其子类的抛出的exception。

当用 exception 模式定义回滚规则时,该模式可以是一个全路径的类名或一个异常类型(必须是 Throwable 的子类)的全路径的类名的子串,目前没有通配符支持。例如,"jakarta.servlet.ServletException" 或 "ServletException" 的值将匹配 jakarta.servlet.ServletException 及其子类。

 

你必须仔细考虑一个模式的具体程度,以及是否包括包的信息(这不是强制性的)。例如,"Exception" 几乎可以匹配任何东西,而且可能会隐藏其他规则。如果 "Exception" 是为了给所有被检查的异常定义一条规则,那么 "java.lang.Exception" 就是正确的。对于更独特的异常名称,例如 "BaseBusinessException",很可能不需要使用异常模式的全称类名称。

此外,基于模式的回滚规则可能会导致对名称相似的异常和嵌套类的非故意匹配。这是因为,如果抛出的异常的名称包含为回滚规则配置的异常模式,那么抛出的异常就被认为是与基于模式的回滚规则相匹配。例如,给定一个配置为匹配 "com.example.CustomException" 的规则,该规则将与名为 com.example.CustomExceptionV2 的异常(与 CustomException 在同一个包中,但有一个额外的后缀)或名为 com.example.CustomException$AnotherException 的异常(作为 CustomException 的嵌套类声明的异常)匹配。

下面的XML片段演示了如何通过通过 rollback-for 属性提供异常模式来配置检查的、特定于应用程序的 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>

如果你不希望事务在抛出异常时被回滚,你也可以指定 'no rollback' (不会滚)规则。下面的例子告诉Spring框架的事务基础架构,即使面对未处理的 InstrumentNotFoundException,也要提交相关的事务:

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

当Spring框架的事务基础架构捕捉到一个异常并咨询配置的回滚规则以决定是否将事务标记为回滚时,最强的匹配规则获胜。因此,在下面的配置中,除了 InstrumentNotFoundException 之外的任何异常都会导致相关事务的回滚:

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

你也可以用编程方式表示需要回滚。虽然简单,但这一过程是相当具有侵入性的,它将你的代码与Spring框架的事务基础结构紧密地联系在一起。下面的例子展示了如何以编程方式指示所需的回滚:

Java

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

如果可能的话,我们强烈建议你使用声明式方法来回滚。如果你绝对需要的话,可以使用编程式回滚,但它的使用与实现一个干净的基于POJO的架构相冲突。

1.4.4. 为不同的ban配置不同的事务性语义

考虑一下这样的情景:你有许多服务层对象,你想对每个对象应用完全不同的事务性配置。你可以通过定义不同的 <aop:advisor/> 元素来实现,这些元素具有不同的 pointcut 和 advice-ref 属性值。

作为比较,首先假设你所有的服务层类都定义在一个根 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 TransactionManager 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 TransactionManager omitted... -->

</beans>

1.4.5. <tx:advice/> 设置

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

  • propagation (传播)设置 是 REQUIRED。
  • isolation (隔离级别)为 DEFAULT.
  • 该事务是读写的。
  • 事务超时默认为底层事务系统的默认超时,如果不支持超时,则为无超时。
  • 任何 RuntimeException 都会触发回滚,而任何被检查的 Exception 都不会。

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

Table 1. <tx:method/> 设置

属性

必须?

默认

说明

name

Yes

事务属性要与之关联的方法名称。通配符(*)可以用来将相同的事务属性设置与许多方法联系起来(例如,get*、handle*、on*Event,等等)。

propagation

No

REQUIRED

事务传播行为。

isolation

No

DEFAULT

事务隔离级别。只适用于传播设置为 REQUIRED 或 REQUIRES_NEW。

timeout

No

-1

事务超时(秒)。只适用于传播 REQUIRED 或 REQUIRES_NEW。

read-only

No

false

读写事务与只读事务。只适用于 REQUIRED 或 REQUIRES_NEW。

rollback-for

No

以逗号分隔的触发回滚的 Exception 实例的列表。例如,com.foo.MyBusinessException,ServletException。

no-rollback-for

No

以逗号分隔的不触发回滚的 Exception 实例列表。例如,com.foo.MyBusinessException,ServletException。

1.4.6. 使用@Transactional

除了基于XML的事务配置的声明式方法外,你还可以使用基于注解的方法。直接在Java源代码中声明事务语义,使声明更接近于受影响的代码。没有太多的过度耦合的危险,因为无论如何,要以事务方式使用的代码几乎总是以这种方式部署。

标准的 jakarta.transaction.Transactional 注解也被支持,可以替代Spring自己的注解。更多细节请参考JTA文档。

使用 @Transactional 注解所带来的易用性最好用一个例子来说明,这将在下面的文字中解释。考虑一下下面的类定义:

Java

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}

如上所述,在类的层面上使用,该注解为声明类(以及它的子类)的所有方法指出了一个默认值。另外,每个方法都可以被单独注解。关于Spring认为哪些方法是事务性的,请参见 方法的可见性和 @Transactional。请注意,类级注解并不适用于类层次结构中的祖先类;在这种情况下,继承的方法需要在本地重新声明,以便参与子类级注解。

当一个POJO类(比如上面的那个)被定义为Spring上下文中的一个Bean时,你可以通过 @Configuration 类中的 @EnableTransactionManagement 注解来使Bean实例具有事务性。详细内容请参见 javadoc。

在XML配置中,<tx:annotation-driven/> 标签提供了类似的便利:

<!-- 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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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 -->
    <!-- a TransactionManager is still required -->
    <tx:annotation-driven transaction-manager="txManager"/> 

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

使Bean实例具有事务性的一行。

如果你想接入的 TransactionManager 的 Bean 名称为 transactionManager,你可以在 <tx:annotation-driven/> 标签中省略 transaction-manager 属性。如果你想依赖注入的 TransactionManager Bean 有其他名称,你必须使用 transaction-manager 属性,就像前面的例子一样。

响应式事务性方法使用响应式返回类型,与命令式编程安排形成对比,如下表所示:

Java

// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    @Override
    public Publisher<Foo> getFoo(String fooName) {
        // ...
    }

    @Override
    public Mono<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}

请注意,对于返回的 Publisher,在 Reactive Streams 取消信号方面有特殊的考虑。更多细节请参见 "使用 TransactionalOperator" 下的 取消信号 部分。

方法的可见性和 @Transactional

当你用Spring的标准配置使用事务代理时,你应该只对具有 public 可见性的方法应用 @Transactional 注解。如果你确实用 @Transactional 注解来注解 protected、private 的或包可见的方法,则不会产生错误,但被注解的方法不会表现出配置的事务性设置。如果你需要注解非 public 的方法,请考虑下一段中关于基于类的代理的提示,或者考虑使用AspectJ编译时或加载时织入(后面描述)。

当在 @Configuration 类中使用 @EnableTransactionManagement 时,通过注册一个自定义的 transactionAttributeSource Bean,也可以使基于类的代理的 protected 或包可见的方法成为事务性的,就像下面的例子一样。然而,请注意,基于接口的代理中的事务性方法必须始终是 public 的,并定义在被代理的接口中。

/**
 * Register a custom AnnotationTransactionAttributeSource with the
 * publicMethodsOnly flag set to false to enable support for
 * protected and package-private @Transactional methods in
 * class-based proxies.
 *
 * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
 */
@Bean
TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}

Spring TestContext 框架默认支持非 private 的 @Transactional 测试方法。例子见测试章节中的 事务管理。

你可以将 @Transactional 注解应用于接口定义、接口上的方法、类定义或类上的方法。然而,仅仅存在 @Transactional 注解并不足以激活事务性行为。@Transactional 注解只是元数据,它可以被一些具有 @Transactional 意识的运行时基础设施所消费,这些基础设施可以使用元数据来配置具有事务行为的适当的Bean。在前面的例子中,<tx:annotation-driven/> 元素开启了事务性行为。

Spring 团队建议你只用 @Transactional 注解来注解具体类(以及具体类的方法),而不是注解接口。你当然可以将 @Transactional 注解放在接口(或接口方法)上,但这只在你使用基于接口的代理时才会有效果。Java注解不从接口继承的事实意味着,如果你使用基于类的代理(proxy-target-class="true")或基于编织的方面(mode="aspectj"),事务设置不会被代理和织入基础设施识别,对象也不会被包裹在一个事务代理中。

在代理模式下(这是默认的),只有通过代理进入的外部方法调用被拦截。这意味着自我调用(实际上,目标对象内的方法调用目标对象的另一个方法)在运行时不会导致实际的事务,即使被调用的方法被标记为 @Transactional。另外,代理必须被完全初始化以提供预期的行为,所以你不应该在初始化代码中依赖这个特性—​例如,在 @PostConstruct 方法中。

如果你希望 self-invocations 也被事务包裹,请考虑使用AspectJ模式(见下表的 mode 属性)。在这种情况下,首先就不存在代理。相反,目标类被织入(也就是说,它的字节码被修改)以支持任何种类的方法上的 @Transactional 运行时行为。

Table 2. 注解驱动的事务设置

XML 属性

注解属性

默认

说明

transaction-manager

N/A (见 TransactionManagementConfigurer javadoc)

transactionManager

要使用的事务管理器的名称。只有当事务管理器的名称不是 transactionManager 时才需要,如前面的例子。

mode

mode

proxy

默认模式(proxy)通过使用Spring的AOP框架(遵循代理语义,如前所述,仅适用于通过代理进入的方法调用)来处理要代理的注释豆。另一种模式(aspectj)则是用Spring的AspectJ事务方面来织入受影响的类,修改目标类的字节码以适用于任何类型的方法调用。AspectJ织入需要在classpath中加入 spring-aspects.jar,并启用加载时织入(或编译时织入)。(参见 Spring配置,了解如何设置加载时织构的细节)。

proxy-target-class

proxyTargetClass

false

仅适用于 proxy mode 。控制为带有 @Transactional 注解的类创建何种类型的事务代理。如果 proxy-target-class 属性被设置为 true,就会创建基于类的代理。如果 proxy-target-class 是 false 的,或者该属性被省略,那么就会创建基于JDK接口的标准代理。(参见 代理机制 以详细了解不同的代理类型)。

order

order

Ordered.

LOWEST_PRECEDENCE

定义应用于用 @Transactional 注解的bean的事务advice的顺序。(关于AOP advice的排序规则的更多信息,请参见 advice 排序)。没有指定排序意味着AOP子系统决定advice的顺序。

 处理 @Transactional 注解的默认 advice 模式是 proxy,它只允许通过代理拦截调用。同一类中的本地调用不能通过这种方式被拦截。对于更高级的拦截模式,可以考虑切换到 aspectj 模式,并结合编译时或加载时织入。

proxy-target-class 属性控制了为带有 @Transactional 注解的类创建何种类型的事务代理。如果 proxy-target-class 被设置为 true,就会创建基于类的代理。如果 proxy-target-class 为 false,或者省略该属性,则创建基于JDK接口的标准代理。(关于不同代理类型的讨论,请参见 代理机制)。 

@EnableTransactionManagement 和 <tx:annotation-driven/> 只在定义它们的同一应用上下文中的bean上寻找 @Transactional。这意味着,如果你把注解驱动的配置放在 DispatcherServlet 的 WebApplicationContext 中,它只在你的控制器而不是服务中检查 @Transactional Bean。更多信息请参见 MVC。  

在评估一个方法的事务性设置时,最派生的位置优先考虑。在下面的例子中,DefaultFooService 类在类的层面上被注解了只读事务的设置,但是同一类中 updateFoo(Foo) 方法上的 @Transactional 注解优先于类层面上定义的事务设置。

Java

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

    public Foo getFoo(String fooName) {
        // ...
    }

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

@Transactional设置

@Transactional 注解是指定接口、类或方法必须具有事务性语义的元数据(例如,"当此方法被调用时,启动一个全新的只读事务,暂停任何现有事务")。默认的 @Transactional 设置如下:

  • propagation (传播)设置为 PROPAGATION_REQUIRED。
  • 隔离级别是 ISOLATION_DEFAULT。
  • 事务是读写的。
  • 事务超时默认为底层事务系统的默认超时,如果不支持超时,则默认为无。
  • 任何 RuntimeException 或 Error 都会触发回滚,而任何被检查的 Exception 则不会。

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

Table 3. @Transactional 设置

属性

类型

说明

value

String

可选的 qualifier,指定要使用的事务管理器。

transactionManager

String

value 别名。

label

String 标签数组,用于为事务添加表达式描述。

标签可以由事务管理器评估,以便将特定于实现的行为与实际事务联系起来。

propagation

enum: Propagation

可选的 propagation (传播)设置。

isolation

enum: Isolation

可选的隔离级别。仅适用于 REQUIRED 或 REQUIRES_NEW 的传播值。

timeout

int (以秒为单位的粒度)

可选的事务超时。仅适用于 REQUIRED 或 REQUIRES_NEW 的传播值。

timeoutString

String (以秒为单位的粒度)

用于指定以秒为单位的 timeout,作为 String 值的替代方案,例如,作为占位符。

readOnly

boolean

读写事务与只读事务。只适用于 REQUIRED 或 REQUIRES_NEW 的值。

rollbackFor

Class 对象的数组,必须从 Throwable 派生。

可选的异常类型数组,必须引起回滚。

rollbackForClassName

异常名称模式(pattern)的数组。

可选的异常名称模式(patterns )数组,必须引起回滚。

noRollbackFor

必须从 Throwable 派生的 Class 对象的数组。

可选的exception类型数组,不能引起回滚。

noRollbackForClassName

异常名称模式(pattern)的数组。

可选的异常名称模式(patterns )数组,不能引起回滚。

关于回滚规则语义、模式(patterns)和基于pattern 的回滚规则可能的非故意匹配的警告的进一步细节,请参见 回滚规则。

目前,你不能显式地控制事务的名称,这里的 'name' 是指出现在事务监视器(如果适用)(例如,WebLogic的事务监视器)和日志输出中的事务名称。对于声明式事务,事务名总是全限定的类名+ . + 事务 advised 类的方法名。例如,如果 BusinessService 类的 handlePayment(..) 方法启动了一个事务,那么事务的名称将是:com.example.BusinessService.handlePayment。

使用@Transactional的多个事务管理器

大多数Spring应用程序只需要一个事务管理器,但也可能出现在一个应用程序中需要多个独立事务管理器的情况。你可以使用 @Transactional 注解的 value 或 transactionManager 属性来选择性地指定要使用的 TransactionManager 的 id。这可以是bean的名字,也可以是事务管理器bean的 qualifier 值。例如,使用 qualifier 符号,你可以在应用程序上下文中把下面的Java代码和下面的事务管理器Bean声明结合起来:

Java

public class TransactionalService {

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

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

    @Transactional("reactive-account")
    public Mono<Void> doSomethingReactive() { ... }
}

下面的列表显示了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>

    <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
        ...
        <qualifier value="reactive-account"/>
    </bean>

在这种情况下,TransactionalService 上的各个方法在不同的事务管理器下运行,由 order、account 和 reactive-account qualifiers 来区分。如果没有找到特别合格的 TransactionManager Bean,默认的 <tx:annotation-driven> 目标 bean 名称 transactionManager 仍然被使用。

自定义“组成”注解

如果你发现你在许多不同的方法上重复使用与 @Transactional 相同的属性, Spring的元注解支持 让你为你的特定用例定义自定义的组成注解。例如,考虑下面的注解定义:

Java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}

前面的注解让我们把上一节的例子写成如下:

Java

public class TransactionalService {

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

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

在前面的例子中,我们使用语法来定义事务管理器 qualifier 和事务标签,但我们也可以包括传播行为、回滚规则、超时和其他功能。

1.4.7. 事务传播(Propagation)

本节介绍了Spring中事务传播的一些语义。请注意,本节并不是对事务传播的正确介绍。相反,它详细介绍了Spring中关于事务传播的一些语义。

在Spring管理的事务中,要注意物理事务和逻辑事务之间的区别,以及传播设置如何适用于这种区别。

理解PROPAGATION_REQUIRED

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

理解PROPAGATION_NESTED

PROPAGATION_NESTED 使用一个具有多个保存点的单一物理事务,它可以回滚到这些保存点。这种部分回滚让内部事务范围触发其范围的回滚,外部事务能够继续物理事务,尽管一些操作已经被回滚。这种设置通常被映射到JDBC保存点(savepoint)上,所以它只适用于JDBC资源事务。参见Spring的 DataSourceTransactionManager。

1.4.8. advice 事务操作

假设你想同时运行事务性操作和一些基本的分析 advice。你如何在 <tx:annotation-driven/> 的context下实现这一点?

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

  • 配置分析 advice 开始。
  • 事务 advice 运行。
  • advice 对象上的方法运行。
  • 事务提交。
  • 分析切面报告了整个事务性方法调用的确切时间。

本章不涉及解释AOP的任何细节(除了它适用于事务)。关于AOP配置和一般AOP的详细内容,请参见 AOP。

下面的代码显示了前面讨论的简单分析切面:

Java

package x.y;

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

Advice 的排序是通过 Ordered 接口控制的。关于 Advice 顺序的全部细节,见 Advice 顺序。

下面的配置创建了一个 fooService Bean,该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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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">
        <!-- run 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 runs 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>

你可以用类似的方式配置任何数量的额外切面。

下面的例子创建了与前两个例子相同的设置,但使用了纯粹的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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://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">
        <!-- run 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.*(..))"/>
        <!-- runs after the profiling advice (cf. 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 TransactionManager here -->

</beans>

前面配置的结果是一个 fooService bean,它按照这个顺序应用了分析和事务性advice。如果你想让分析advice在事务advice之后运行,在事务advice之前运行,你可以交换分析切面Bean的 order 属性的值,使其高于事务advice的顺序值。

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

1.4.9. 在 AspectJ 中使用@Transactional

你也可以通过AspectJ方面在Spring容器之外使用Spring框架的 @Transactional 支持。要做到这一点,首先用 @Transactional 注解来注解你的类(以及可选的类的方法),然后用 spring-aspects.jar 文件中定义的 org.springframework.transaction.aspectj.AnnotationTransactionAspect 链接(织入)你的应用程序。你还必须用一个事务管理器来配置这个方面。你可以使用Spring框架的IoC容器来处理切面的依赖注入。配置事务管理方面的最简单方法是使用 <tx:annotation-driven/> 元素,并将 mode 属性指定为 aspectj,如使用 使用 @Transactional 中所述。因为我们在这里关注的是在Spring容器外运行的应用程序,所以我们将向你展示如何以编程的方式进行配置。

在继续之前,你可能需要分别阅读使用 使用 @Transactional 和 AOP 。

下面的例子显示了如何创建一个事务管理器并配置 AnnotationTransactionAspect 来使用它:

Java

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

当你使用这个切面时,你必须注解实现类(或该类中的方法或两者),而不是该类实现的接口(如果有)。AspectJ遵循Java的规则,即接口上的注解是不继承的。

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

类中的方法上的 @Transactional 注解覆盖了类注解给出的默认事务语义(如果存在)。你可以对任何方法进行注解,无论其可见性如何。

要用 AnnotationTransactionAspect 织入你的应用程序,你必须用AspectJ构建你的应用程序(见 AspectJ开发指南)或使用运行时织入。请参阅 Spring框架中使用 AspectJ 的运行时织入,了解使用AspectJ的运行时织入的讨论。

1.5. 编程式事务管理

Spring框架提供了两种编程式事务管理的手段,通过使用:

  • TransactionTemplate 或 TransactionalOperator.
  • 直接实现一个 TransactionManager。

Spring团队通常推荐 TransactionTemplate 用于强制性流程中的编程式事务管理, TransactionalOperator 用于响应式代码。第二种方法类似于使用JTA的 UserTransaction API,尽管异常处理没有那么麻烦。

1.5.1. 使用TransactionTemplate

TransactionTemplate 采用了与其他Spring template 相同的方法,例如 JdbcTemplate。它使用回调方法(将应用程序代码从获取和释放事务资源的模板中解放出来),并导致代码的意图驱动,即你的代码只关注你想做的事情。

正如下面的示例所示,使用 TransactionTemplate 会将你与 Spring 的事务基础设施和API绝对地耦合在一起。是否适合使用编程式事务管理是你需要自己决定的,这取决于你的开发需求。

必须在事务性上下文(transactional context)中运行并明确使用 TransactionTemplate 的应用程序代码类似于下一个例子。你,作为一个应用程序的开发者,可以写一个 TransactionCallback 的实现(通常表示为一个匿名的内部类),其中包含你需要在事务上下文中运行的代码。然后你可以把你的自定义 TransactionCallback 的一个实例传递给 TransactionTemplate 上暴露的 execute(..) 方法。下面的例子显示了如何做到这一点:

Java

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 runs in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果没有返回值,你可以使用方便的 TransactionCallbackWithoutResult 类与一个匿名类,如下所示:

Java

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

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

Java

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

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

指定事务设置

你可以通过编程或配置在 TransactionTemplate 上指定事务设置(如传播模式、隔离级别、超时等等)。默认情况下,TransactionTemplate 实例有 默认的事务设置。下面的例子显示了对一个特定的 TransactionTemplate 的事务性设置的编程式定制:

Java

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:

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

然后,你可以将 sharedTransactionTemplate 注入所需的许多服务中。

最后,TransactionTemplate 类的实例是线程安全的,因为实例不维护任何对话状态。然而, TransactionTemplate 实例确实维护配置状态。因此,虽然许多类可以共享 TransactionTemplate 的一个实例,但如果一个类需要使用具有不同设置的 TransactionTemplate(例如,不同的隔离级别),你需要创建两个不同的 TransactionTemplate 实例。

1.5.2. 使用TransactionalOperator

TransactionalOperator 遵循与其他响应式操作符相似的操作设计。它使用回调方法(使应用程序代码不必做模板式的获取和释放事务性资源),导致代码是意图驱动的,即你的代码只关注你想做的事情。

正如下面的例子所示,使用 TransactionalOperator 绝对能让你与Spring的事务基础架构和API结合起来。编程式事务管理是否适合你的开发需求,你必须自己做出决定。

必须在事务性上下文(transactional context)中运行并且明确使用 TransactionalOperator 的应用程序代码类似于下一个例子:

Java

public class SimpleService implements Service {

    // single TransactionalOperator shared amongst all methods in this instance
    private final TransactionalOperator transactionalOperator;

    // use constructor-injection to supply the ReactiveTransactionManager
    public SimpleService(ReactiveTransactionManager transactionManager) {
        this.transactionalOperator = TransactionalOperator.create(transactionManager);
    }

    public Mono<Object> someServiceMethod() {

        // the code in this method runs in a transactional context

        Mono<Object> update = updateOperation1();

        return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
    }
}

TransactionalOperator 可以以两种方式使用:

  • 使用 Project Reactor 类型的操作风格 (mono.as(transactionalOperator::transactional))
  • 其他所有情况下的回调风格 (transactionalOperator.execute(TransactionCallback<T>))

回调中的代码可以通过调用所提供的 ReactiveTransaction 对象的 setRollbackOnly() 方法来回滚事务,如下所示:

Java

transactionalOperator.execute(new TransactionCallback<>() {

    public Mono<Object> doInTransaction(ReactiveTransaction status) {
        return updateOperation1().then(updateOperation2)
                    .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
        }
    }
});

取消信号

在Reactive Streams中,Subscriber 可以取消其 Subscription 并停止其 Publisher。Project Reactor 以及其他库中的操作符,如 next()、take(long)、timeout(Duration) 等,都可以发出取消指令。我们无法知道取消的原因,是由于错误还是仅仅是对进一步消费缺乏兴趣。从5.3版本开始,取消信号会导致回滚。因此,考虑事务 Publisher 下游使用的操作符是很重要的。特别是在 Flux 或其他多值 Publisher 的情况下,必须消耗全部输出,以使事务完成。

指定事务设置

你可以为 TransactionalOperator 指定事务设置(如传播模式、隔离级别、超时等等)。默认情况下, TransactionalOperator 实例有 默认的事务设置。下面的例子显示了对特定 TransactionalOperator 的事务性设置的定制:

Java

public class SimpleService implements Service {

    private final TransactionalOperator transactionalOperator;

    public SimpleService(ReactiveTransactionManager transactionManager) {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

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

        this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
    }
}

1.5.3. 使用TransactionManager

下面几节解释了命令式和响应式事务管理器的编程式用法。

使用PlatformTransactionManager

对于强制性事务,你可以直接使用 org.springframework.transaction.PlatformTransactionManager 来管理你的事务。要做到这一点,通过一个Bean引用将你使用的 PlatformTransactionManager 的实现传递给你的Bean。然后,通过使用 TransactionDefinition 和 TransactionStatus 对象,你可以启动事务、回滚和提交。下面的例子展示了如何做到这一点:

Java

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

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

使用ReactiveTransactionManager

当使用响应式事务时,你可以直接使用 org.springframework.transaction.ReactiveTransactionManager 来管理你的事务。要做到这一点,通过一个Bean引用将你使用的 ReactiveTransactionManager 的实现传递给你的Bean。然后,通过使用 TransactionDefinition 和 ReactiveTransaction 对象,你可以启动事务、回滚和提交。下面的例子展示了如何做到这一点:

Java

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);

reactiveTx.flatMap(status -> {

    Mono<Object> tx = ...; // put your business logic here

    return tx.then(txManager.commit(status))
            .onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});

1.6. 在编程式和声明式事务管理之间做出选择

通常只有当你有少量的事务性操作时,编程式事务管理才是一个好主意。例如,如果你有一个web应用程序,只需要对某些更新操作进行事务,你可能不想通过使用Spring或其他技术来设置事务代理。在这种情况下,使用 TransactionTemplate 可能是一个好办法。能够明确地设置事务名称也是只有通过使用事务管理的编程式方法才能做到的。

另一方面,如果你的应用程序有许多事务性操作,声明式事务管理通常是值得的。它将事务管理从业务逻辑中分离出来,并且不难配置。当使用Spring框架,而不是EJB CMT时,声明式事务管理的配置成本会大大降低。

1.7. 事务绑定的事件

从Spring 4.2开始,事件的监听器可以被绑定到事务的某个阶段。典型的例子是在事务成功完成时处理该事件。这样做可以让事件在当前事务的结果对监听器来说实际上很重要的时候被更灵活地使用。

你可以通过使用 @EventListener 注解来注册一个普通的事件监听器。如果你需要将其绑定到事务上,请使用 @TransactionalEventListener。当你这样做的时候,监听器默认被绑定到事务的提交阶段。

下一个例子显示了这个概念。假设一个组件发布了一个创建订单的事件,我们想定义一个监听器,它只在发布该事件的事务成功提交后处理该事件。下面的例子设置了这样一个事件监听器:

Java

@Component
public class MyComponent {

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

@TransactionalEventListener 注解暴露了一个 phase 属性,让你自定义监听器应该被绑定的事务的阶段。有效的阶段是 BEFORE_COMMIT、AFTER_COMMIT(默认)、AFTER_ROLLBACK,以及 AFTER_COMPLETION,它聚集了事务完成(无论是提交还是回滚)。

如果没有事务在运行,监听器根本就不会被调用,因为我们无法满足所需的语义。然而,你可以通过将注解的 fallbackExecution 属性设置为 true 来覆盖这一行为。

@TransactionalEventListener 只适用于由 PlatformTransactionManager 管理的线程绑定的事务。由 ReactiveTransactionManager 管理的响应式事务使用 Reactor 上下文而不是线程本地(thread-local)属性,所以从事件监听器的角度来看,没有兼容的活动事务,它可以参与其中。

1.8. 特定的应用服务器整合

Spring的事务抽象通常与应用服务器无关。此外,Spring的 JtaTransactionManager 类(可以选择执行JNDI lookup JTA UserTransaction 和 TransactionManager 对象)会自动检测后者对象的位置,这因应用服务器而异。对JTA TransactionManager 的访问允许增强事务语义—​特别是支持事务暂停。详情见 JtaTransactionManager javadoc。

Spring的 JtaTransactionManager 是在Jakarta EE应用服务器上运行的标准选择,并且已知可以在所有普通服务器上运行。高级功能,如事务暂停,也可以在许多服务器上运行(包括GlassFish、JBoss和Geronimo),无需任何特殊配置。然而,对于完全支持的事务暂停和进一步的高级集成,Spring包含了针对WebLogic Server和WebSphere的特殊适配器。这些适配器将在下面的章节中讨论。

对于标准场景,包括WebLogic Server和WebSphere,考虑使用方便的 <tx:jta-transaction-manager/> 配置元素。配置后,该元素会自动检测底层服务器,并选择该平台可用的最佳事务管理器。这意味着你不需要明确地配置特定于服务器的适配器类(如在下面的章节中讨论的)。相反,它们是自动选择的,标准的 JtaTransactionManager 是默认的 fallback。

1.8.1. IBM WebSphere

在 WebSphere 6.1.0.9 及以上版本,推荐使用的 Spring JTA 事务管理器是 WebSphereUowTransactionManager。这个特殊的适配器使用 IBM 的 UOWManager API,它在 WebSphere Application Server 6.1.0.9 及以上版本中可用。通过这个适配器,Spring 驱动的事务暂停(由 PROPAGATION_REQUIRES_NEW 发起的暂停和恢复)得到了 IBM 的正式支持。

1.8.2. Oracle WebLogic Server

在WebLogic Server 9.0或以上版本中,你通常会使用 WebLogicJtaTransactionManager 而不是现有的 JtaTransactionManager 类。这个普通 JtaTransactionManager 的特殊WebLogic专用子类在WebLogic管理的事务环境中支持Spring事务定义的全部功能,超出了标准JTA语义。其特点包括事务名称、每个事务的隔离级别以及在所有情况下正确恢复事务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/68164.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Malloc动态内存分配

在C语言中我们会使用malloc来动态地分配内存&#xff0c;这样做的一个主要理由是有些数据结构的大小只有在运行时才能确定。例如&#xff0c;如果你正在编写一个程序&#xff0c;需要用户输入一些数据&#xff0c;但你不知道用户会输入多少数据&#xff0c;那么你就需要使用动态…

vue3使用pinia和pinia-plugin-persist做持久化存储

1、安装依赖 pnpm i pinia // 安装 pinia pnpm i pinia-plugin-persist // 安装持久化存储插件2、main.js引入 import App from ./App.vue const app createApp(App)//pinia import { createPinia } from pinia import piniaPersist from pinia-plugin-persist //持久化插件 …

Python中enumerate用法详解

目录 1.简介 2.语法 3.参数 4.返回值 5.详解 6.实例 7.补充 1.简介 enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列&#xff0c;同时列出数据和数据下标&#xff0c;一般用在 for 循环当中。 2.语法 以下是 enumerate() 方法的语…

Qt应用开发(基础篇)——堆栈窗口 QStackedWidget

一、前言 QStackedWidget继承于QFrame&#xff0c;QFrame继承于QWidget&#xff0c;是Qt常用的堆栈窗口部件。 框架类QFrame介绍 QStackedWidget堆栈窗口&#xff0c;根据下标切换&#xff0c;一次显示一个小部件&#xff0c;常用于应用界面切换、图片轮询播放等场景。 二、QSt…

通义千问开源模型部署使用

首先可以参考modelScope社区给出的使用文档&#xff0c;已经足够全面 通义千问-7B-Chat 但在按照文档中步骤部署时&#xff0c;还是有些错误问题发生&#xff0c;可以搜索参考的解决方式不多&#xff0c;所以记录下来 个人电脑部署 这里不太建议使用自己的笔记本部署通义千…

exchange partition global index

EXCHANGE PARTITION with a Table having a UNIQUE INDEX and PK Constraint. (Doc ID 1620636.1)​编辑To Bottom In this Document Symptoms Changes Cause Solution References APPLIES TO: Oracle Database - Enterprise Edition - Version 11.2.0.3 and later Oracle Da…

Spring整合MyBatis(详细步骤)

Spring与Mybatis的整合&#xff0c;大体需要做两件事&#xff0c; 第一件事是:Spring要管理MyBatis中的SqlSessionFactory 第二件事是:Spring要管理Mapper接口的扫描 具体的步骤为: 步骤1:项目中导入整合需要的jar包 <dependency><!--Spring操作数据库需要该jar包…

gazebo 导入从blender导出的dae等文件

背景&#xff1a; gazebo 模型库里的模型在我需要完成的任务中不够用&#xff0c;还是得从 solidworks、3DMax, blender这种建模软件里面在手动画一些&#xff0c;或者去他们的库里面在挖一挖。 目录 1 blender 1-1 blender 相关links 1-2 install 2 gazebo导入模型 2-1 g…

湘大 XTU OJ 1308 比赛 题解:循环结束的临界点+朴素模拟

一、链接 比赛 二、题目 题目描述 有n个人要进行比赛&#xff0c;比赛规则如下&#xff1a; 假设每轮比赛的人是m&#xff0c;取最大的k&#xff0c;k2^t且k≤m。这k个人每2人举行一场比赛&#xff0c;胜利者进入一下轮&#xff0c;失败者被淘汰。余下的m-k个人&#xff0…

从Spring源码看创建对象的过程

从Spring源码看创建对象的过程 Spring对于程序员set注入的属性叫做属性的填充、对于set注入之后的处理&#xff08;包括BeanPostProcessor的处理、初始化方法的处理&#xff09;叫做初始化。 研读AbstractBeanFactory类中的doGetBean()方法 doGetBean()方法首先完成的工作是…

mysql基础之触发器的简单使用

1.建立学生信息表 -- 触发器 -- 建立学生信息表 create table s1(id int unsigned auto_increment,name varchar(30),score tinyint unsigned,dept varchar(50),primary key(id) );2.建立学生补考信息表 -- 建立学生补考信息表 create table s2 like s1;3.建立触发器&#xf…

Grafana技术文档-概念-《十分钟扫盲》

Grafana官网链接 Grafana: The open observability platform | Grafana Labs 基本概念 Grafana是一个开源的度量分析和可视化套件&#xff0c;常用于对大量数据进行实时分析和可视化。以下是Grafana的基本概念&#xff1a; 数据源&#xff08;Data Source&#xff09;&#…

【大数据】Flink 详解(一):基础篇

Flink 详解&#xff08;一&#xff09;&#xff1a;基础篇 1、什么是 Flink &#xff1f; Flink 是一个以 流 为核心的高可用、高性能的分布式计算引擎。具备 流批一体&#xff0c;高吞吐、低延迟&#xff0c;容错能力&#xff0c;大规模复杂计算等特点&#xff0c;在数据流上提…

模板的进阶

目录 1.非类型模板参数 2.模板特化 2.1概念 2.2函数模板特化 2.3类模板特化 2.3.1全特化 2.3.2偏特化 3.模板分离编译 3.1什么是分离编译 3.2 模板的分离编译 3.3解决方法 4. 模板总结 1.非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a…

Python(七十五--总结)列表、字典、元组、集合总结

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

关于Object 0 = new Object() 的追魂九连问

文章目录 对象的创建过程对象的组成解析普通对象**结果分析&#xff1a;**给对象添加属性注意事项 补充jvm压缩指针栗子&#xff1a; 对象头包含什么对象怎么定位&#xff1f;**句柄方式和直接引用的优缺点&#xff1a;** 对象怎么分配&#xff1f;为什么hotspot不使用c对象来代…

QT的信号槽的四种写法和五种链接方式

目录 四种信号槽写法&#xff1a; 五种连接方式&#xff1a; 实例&#xff1a; 常见错误及改正&#xff1a; 错误1: 未连接信号与槽 错误2: 信号和槽参数不匹配 错误3: 未使用Q_OBJECT宏 错误4: 跨线程连接未处理 在Qt中&#xff0c;信号&#xff08;Signal&#xff09…

Stephen Wolfram:让 ChatGPT 真正起作用的是什么?

What Really Lets ChatGPT Work? 让 ChatGPT 真正起作用的是什么&#xff1f; Human language—and the processes of thinking involved in generating it—have always seemed to represent a kind of pinnacle of complexity. And indeed it’s seemed somewhat remarkabl…

go-admin 使用开发

在项目中使用redis 作为数据缓存&#xff1a;首先引入该包 “github.com/go-redis/redis/v8” client : redis.NewClient(&redis.Options{Addr: config.QueueConfig.Redis.Addr, // Redis 服务器地址Password: config.QueueConfig.Redis.Password, // Redis 密码&…

Vue自定义指令使用

本篇文章讲述使用Vue自定义指令&#xff0c;并在项目中完成相应功能。 在平常Vue脚手架项目中&#xff0c;使用到 自定义指令较少&#xff0c;一般都是使用的自带指令&#xff0c;比如 v-show 、v-if 、 v-for 、 v-bind 之类的。这些已经能够满足大多数项目使用。更多的可能也…