Appearance
Spring 数据访问:事务管理
Tip:基于 Spring Core 5.3.30 版本。
全面的事务支持是使用 Spring 框架最引人注目的原因之一。Spring 框架为事务管理提供了一致的抽象,带来以下好处:
在不同的事务 API 之间提供一致的编程模型,如 Java 事务 API(JTA,Java Transaction API)、JDBC、Hibernate 和 Java 持久性 API(JPA,Java Persistence API);
支持声明式事务管理;
比复杂的事务 API(如 JTA)更简单的编程式事务管理 API;
与 Spring 的数据访问抽象的优秀集成;
以下各节描述了 Spring 框架的事务特性和技术:
Spring 框架的事务支持模型的优点描述了你为什么会使用 Spring 框架的事务抽象,而不是 EJB 容器管理的事务(CMT,Container-Managed Transactions)或选择通过专有 API(如 Hibernate)驱动本地事务;
理解 Spring 框架的事务抽象概述了核心类,并描述了如何从各种来源配置和获取
DataSource实例;事务资源同步描述了应用程序代码如何确保资源被正确地创建、重用和清理;
声明式事务管理描述了对声明式事务管理的支持;
编程式事务管理涵盖了对编程式(即显式编码的)事务管理的支持;
事务绑定事件描述了你如何在事务中使用应用程序事件;
本章还包括了最佳实践、应用服务器集成和常见问题解决方案的讨论。
1. Spring 框架的事务支持模型的优点
传统上,Java EE 开发者在事务管理上有两个选择:全局事务或本地事务,这两者都有深刻的限制。全局和本地事务管理将在接下来的两节中进行回顾,然后是关于 Spring 框架的事务管理支持如何解决全局和本地事务模型的限制的讨论。
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.2. 本地事务
本地事务是特定于资源的,例如与 JDBC 连接关联的事务。本地事务可能更容易使用,但有一个显著的缺点:它们不能在多个事务资源之间工作。例如,使用 JDBC 连接管理事务的代码不能在全局 JTA 事务中运行。因为应用服务器不参与事务管理,所以它不能确保跨多个资源的正确性。(值得注意的是,大多数应用程序使用单一的事务资源。)另一个缺点是,本地事务对编程模型具有侵入性。
1.3. Spring 框架的一致性编程模型
Spring 解决了全局和本地事务的缺点。它让应用开发者在任何环境中使用一致的编程模型。你只需编写一次代码,就可以在不同环境中受益于不同的事务管理策略。Spring 框架提供了声明式和编程式两种事务管理。大多数用户更喜欢声明式事务管理,这也是我们在大多数情况下的推荐。
在编程式事务管理中,开发者使用 Spring 框架的事务抽象,它可以运行在任何底层的事务基础设施之上。在首选的声明式模型中,开发者通常只需要编写少量或不需要编写与事务管理相关的代码,因此,他们不依赖于 Spring 框架的事务 API 或任何其他事务 API。
Note:你需要一个用于事务管理的应用服务器么?
Spring Framework 的事务管理支持改变了企业级 Java 应用需要应用服务器的传统规则。
特别是,你并不需要一个纯粹通过 EJBs 进行声明式事务的应用服务器。事实上,即使你的应用服务器具有强大的 JTA 功能,你也可能会决定 Spring Framework 的声明式事务比 EJB CMT 提供了更强大的功能和更高效的编程模型。
通常,只有当你的应用需要处理跨多个资源的事务时,你才需要应用服务器的 JTA 功能,这对许多应用来说并不是必需的。许多高端应用使用单一的、高度可扩展的数据库(如 Oracle RAC)。独立的事务管理器(如 Atomikos Transactions 和 JOTM)是其他的选择。当然,你可能需要其他的应用服务器功能,如 Java 消息服务(JMS,Java Message Service)和 Java EE 连接器架构(JCA,Java EE Connector Architecture)。
Spring Framework 让你可以选择何时将你的应用扩展到一个完全加载的应用服务器。使用 EJB CMT 或 JTA 的唯一替代方案是编写具有本地事务(如那些在 JDBC 连接上的)的代码,并在需要在全局的、容器管理的事务中运行该代码时面临大量的重构工作的日子已经一去不复返了。有了 Spring Framework,你的配置文件中只有一部分的 Bean 定义需要改变(而不是你的代码)。
2. 理解 Spring 框架的事务抽象
Spring 事务抽象的关键在于事务策略的概念。事务策略由 TransactionManager 定义,特别是 org.springframework.transaction.PlatformTransactionManager 接口用于命令式事务管理,以及 org.springframework.transaction.ReactiveTransactionManager 接口用于响应式事务管理。以下清单显示了 PlatformTransactionManager API 的定义:
Java
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}1
2
3
4
5
2
3
4
5
这主要是一个服务提供者接口(SPI,Service Provider Interface),尽管你可以在你的应用代码中以编程方式使用它。因为 PlatformTransactionManager 是一个接口,所以可以根据需要轻松地进行模拟(Mocked)或存根(Stubbed)。它并不绑定到查找策略,如 JNDI。PlatformTransactionManager 的实现像 Spring Framework IoC 容器中的任何其他对象(或 Bean)一样被定义。仅此一点就使得 Spring Framework 的事务成为值得的抽象,即使你正在使用 JTA。你可以比直接使用 JTA 更容易地测试事务代码。
再次,符合 Spring 的哲学,可以由 PlatformTransactionManager 接口的任何方法抛出的 TransactionException 是未经检查的(也就是说,它扩展了 java.lang.RuntimeException 类)。事务基础设施失败几乎总是致命的。在罕见的情况下,应用代码实际上可以从事务失败中恢复,应用开发者仍然可以选择捕获并处理 TransactionException。重要的一点是,开发者并不被强制这样做。
getTransaction(..) 方法返回一个 TransactionStatus 对象,取决于一个 TransactionDefinition 参数。返回的 TransactionStatus 可能代表一个新的事务,或者如果在当前调用堆栈中存在匹配的事务,可以代表一个现有的事务。在这后一种情况下的含义是,与 Java EE 事务上下文一样,TransactionStatus 与执行的线程相关联。
从 Spring Framework 5.2 开始,Spring 还为使用响应式类型或 Kotlin Coroutines 的响应式应用提供了事务管理抽象。以下清单显示了由 org.springframework.transaction.ReactiveTransactionManager 定义的事务策略:
Java
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;
}1
2
3
4
5
2
3
4
5
响应式事务管理器主要是一个服务提供者接口(SPI),尽管你可以在你的应用代码中以编程方式使用它。因为 ReactiveTransactionManager 是一个接口,所以可以根据需要轻松地进行模拟或存根。
TransactionDefinition 接口指定了:
传播:通常,事务范围内的所有代码都在该事务中运行。然而,如果在已经存在事务上下文的情况下运行事务方法,你可以指定行为。例如,代码可以继续在现有的事务中运行(常见情况),或者可以挂起现有的事务并创建一个新的事务。Spring 提供了所有熟悉的来自 EJB CMT 的事务传播选项。要阅读关于 Spring 中事务传播的语义,请参阅事务传播;
隔离:这个事务与其他事务的工作隔离的程度。例如,这个事务是否可以看到其他事务的未提交写入;
超时:这个事务在超时并被底层事务基础设施自动回滚之前运行多久;
只读状态:当你的代码读取但不修改数据时,你可以使用只读事务。在某些情况下,只读事务可以是一个有用的优化,例如当你使用 Hibernate 时;
这些设置反映了标准的事务概念。如果需要,可以参考讨论事务隔离级别和其他核心事务概念的资源。理解这些概念对于使用 Spring Framework 或任何事务管理解决方案都是必要的。
TransactionStatus 接口为事务代码提供了一个简单的方式来控制事务执行和查询事务状态。这些概念应该是熟悉的,因为它们是所有事务 API 的共性。以下清单显示了 TransactionStatus 接口:
Java
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
@Override
boolean isNewTransaction();
boolean hasSavepoint();
@Override
void setRollbackOnly();
@Override
boolean isRollbackOnly();
void flush();
@Override
boolean isCompleted();
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
无论你选择在 Spring 中使用声明式还是编程式事务管理,定义正确的 TransactionManager 实现都是绝对必要的。你通常通过依赖注入来定义这个实现。
TransactionManager 的实现通常需要了解它们工作的环境:JDBC、JTA、Hibernate 等等。以下示例展示了如何定义一个本地 PlatformTransactionManager 实现(在这种情况下,使用纯 JDBC)。
你可以通过创建类似于以下的 Bean 来定义一个 JDBC DataSource:
XML
<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>1
2
3
4
5
6
2
3
4
5
6
然后,相关的 PlatformTransactionManager Bean 定义将引用 DataSource 定义。它应该类似于以下的示例:
XML
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>1
2
3
2
3
如果你在 Java EE 容器中使用 JTA,那么你将使用一个容器 DataSource,通过 JNDI 获取,与 Spring 的 JtaTransactionManager 结合使用。以下示例显示了 JTA 和 JNDI 查找版本的样子:
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: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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
JtaTransactionManager 不需要知道 DataSource(或任何其他特定资源),因为它使用的是容器的全局事务管理基础设施。
Note:前面的
dataSourceBean 的定义使用了来自jee命名空间的<jndi-lookup/>标签。更多信息请参见 JEE Schema。
Note:如果你使用 JTA,无论你使用的是什么数据访问技术,无论是 JDBC、Hibernate JPA,还是任何其他支持的技术,你的事务管理器定义应该看起来都是一样的。这是因为 JTA 事务是全局事务,可以征集(Enlist)任何事务资源。
在所有 Spring 事务设置中,应用代码不需要改变。你可以仅通过改变配置来改变事务是如何管理的,即使那个改变意味着从本地事务移动到全局事务,反之亦然。
2.1. Hibernate 事务设置
你也可以很容易地使用 Hibernate 本地事务,如下面的示例所示。在这种情况下,你需要定义一个 Hibernate LocalSessionFactoryBean,你的应用代码可以使用它来获取 Hibernate Session 实例。
DataSource Bean 的定义与前面显示的本地 JDBC 示例类似,因此在下面的示例中没有显示。
Note:如果
DataSource(由任何非 JTA 事务管理器使用)通过 JNDI 查找并由 Java EE 容器管理,那么它应该是非事务性的,因为 Spring Framework(而不是 Java EE 容器)管理事务。
在这种情况下,txManager Bean 是 HibernateTransactionManager 类型。与 DataSourceTransactionManager 需要引用 DataSource 一样,HibernateTransactionManager 需要引用 SessionFactory。以下示例声明了 sessionFactory 和 txManager Bean:
XML
<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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如果你使用 Hibernate 和 Java EE 容器管理的 JTA 事务,你应该使用与前面的 JDBC 的 JTA 示例中相同的 JtaTransactionManager,如下面的示例所示。此外,建议通过其事务协调器,可能还有其连接释放模式配置,让 Hibernate 知道 JTA:
XML
<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"/>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
或者,你也可以将 JtaTransactionManager 传递到你的 LocalSessionFactoryBean 中,以强制执行相同的默认设置:
XML
<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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3. 事务资源同步
如何创建不同的事务管理器,以及它们如何与需要与事务同步的相关资源(例如,DataSourceTransactionManager 到 JDBC DataSource,HibernateTransactionManager 到 Hibernate SessionFactory 等)链接,现在应该很清楚了。本节描述了应用代码(直接或间接地,通过使用如 JDBC、Hibernate 或 JPA 等持久性 API)如何确保这些资源被正确地创建、重用和清理。本节还讨论了如何通过相关的 TransactionManager(可选地)触发事务同步。
3.1. 高级同步方法
首选的方法是使用 Spring 的最高级别的基于模板的持久性集成 API,或者使用具有事务感知工厂 Bean 或代理的本地 ORM API 来管理本地资源工厂。这些事务感知的解决方案在内部处理资源的创建和重用、清理、资源的可选事务同步以及异常映射。因此,用户数据访问代码不需要处理这些任务,而只需要专注于非样板持久性逻辑。通常,你使用本地 ORM API 或者使用 JdbcTemplate 采取模板方法进行 JDBC 访问。这些解决方案在本参考文档的后续部分中有详细说明。
3.2. 低级同步方法
诸如 DataSourceUtils(用于 JDBC)、EntityManagerFactoryUtils(用于 JPA)、SessionFactoryUtils(用于 Hibernate)等类存在于较低的级别。当你希望应用代码直接处理本地持久性 API 的资源类型时,你使用这些类来确保获得适当的 Spring Framework 管理的实例,事务(可选地)同步,以及在过程中发生的异常被正确地映射到一致的 API。
例如,在 JDBC 的情况下,你可以使用 Spring 的 org.springframework.jdbc.datasource.DataSourceUtils 类,而不是传统的 JDBC 方法在 DataSource 上调用 getConnection() 方法,如下所示:
Java
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 使用,正确的连接检索会在幕后进行,你不需要编写任何特殊的代码。
3.3. TransactionAwareDataSourceProxy
在最底层存在 TransactionAwareDataSourceProxy 类。这是一个代理目标 DataSource 的代理,它包装了目标 DataSource 以增加对 Spring 管理的事务的感知。在这方面,它类似于由 Java EE 服务器提供的事务性 JNDI DataSource。
除非必须调用现有的代码并传递一个标准的 JDBC DataSource 接口实现,否则你几乎永远不需要或不想使用这个类。在这种情况下,这段代码可能是可用的,但却参与了 Spring 管理的事务。你可以使用前面提到的更高级的抽象来编写你的新代码。
4. 声明式事务管理
Note:大多数 Spring 框架的用户选择声明式事务管理。这个选项对应用程序代码的影响最小,因此,最符合非侵入性轻量级容器的理念。
Spring 框架的声明式事务管理是通过 Spring 的面向切面编程(AOP)实现的。然而,由于事务切面的代码随 Spring 框架分发,并且可以以样板的方式使用,所以通常不需要理解 AOP 的概念就可以有效地使用这段代码。
Spring 框架的声明式事务管理与 EJB CMT 类似,你可以指定到单个方法级别的事务行为(或不指定)。如果需要,你可以在事务上下文中调用 setRollbackOnly()。两种类型的事务管理之间的区别包括:
与 EJB CMT 不同,后者与 JTA 绑定,Spring 框架的声明式事务管理在任何环境中都可以工作。它可以通过调整配置文件,使用 JDBC、JPA 或 Hibernate 来处理 JTA 事务或本地事务;
你可以将 Spring 框架的声明式事务管理应用到任何类,而不仅仅是特殊类,如 EJB;
Spring 框架提供了声明式回滚规则,这是 EJB 没有的功能。对回滚规则提供了编程式和声明式的支持;
Spring 框架允许你使用 AOP 自定义事务行为。例如,你可以在事务回滚的情况下插入自定义行为。你还可以添加任意增强,以及事务增强。使用 EJB CMT,你不能影响容器的事务管理,除非使用
setRollbackOnly();Spring 框架不支持跨远程调用的事务上下文传播,如高端应用服务器所做的那样。如果你需要这个功能,我们建议你使用 EJB。然而,在使用这样的功能之前,请仔细考虑,因为,通常,人们不希望事务跨越远程调用;
回滚规则的概念很重要。它们让你指定哪些异常(和可抛出的)应该导致自动回滚。你可以在配置中声明式地指定这一点,而不是在 Java 代码中。所以,虽然你仍然可以在 TransactionStatus 对象上调用 setRollbackOnly() 来回滚当前的事务,但最常见的是你可以指定一个规则,即 MyApplicationException 必须始终导致回滚。这个选项的显著优点是,业务对象不依赖于事务基础设施。例如,它们通常不需要导入 Spring 事务 API 或其他 Spring API。
尽管 EJB 容器默认行为会在系统异常(通常是运行时异常)上自动回滚事务,但 EJB CMT 在应用程序异常(即,除 java.rmi.RemoteException 之外的已检查异常)上不会自动回滚事务。虽然 Spring 对声明式事务管理的默认行为遵循 EJB 约定(只有在未检查的异常上回滚是自动的),但自定义这种行为通常是有用的。
4.1. 理解 Spring 框架的声明式事务实现
仅仅告诉你用 @Transactional 注解你的类,将 @EnableTransactionManagement 添加到你的配置中,并期望你理解所有的工作原理是不够的。为了提供更深入的理解,本节将在事务相关问题的背景下解释 Spring 框架的声明式事务基础设施的内部工作原理。
关于 Spring 框架的声明式事务支持,最重要的概念是这种支持是通过 AOP 代理启用的,而事务增强是由元数据(目前是基于 XML 或注解)驱动的。AOP 与事务元数据的结合产生了一个 AOP 代理,该代理使用 TransactionInterceptor 与适当的 TransactionManager 实现一起驱动方法调用周围的事务。
Note:Spring AOP 在 AOP 部分有所介绍。
Spring 框架的 TransactionInterceptor 为命令式和响应式编程模型提供事务管理。拦截器通过检查方法返回类型来检测所需的事务管理风格。返回响应式类型(如 Publisher 或 Kotlin Flow 或其子类型)的方法符合响应式事务管理。所有其他返回类型(包括 void)都使用命令式事务管理的代码路径。
事务管理风格影响所需的事务管理器。命令式事务需要 PlatformTransactionManager,而响应式事务使用 ReactiveTransactionManager 实现。
Note
@Transactional通常与PlatformTransactionManager管理的线程绑定事务一起工作,将事务暴露给当前执行线程中的所有数据访问操作。注意:这并不传播到方法内新启动的线程。由
ReactiveTransactionManager管理的响应式事务使用 Reactor 上下文而不是 thread-local 属性。因此,所有参与的数据访问操作都需要在同一响应器上下文中的同一响应管道中执行。
下面的图片显示了在事务代理上调用方法的概念视图:

4.2. 声明式事务实现的示例
请考虑以下接口及其相应的实现。这个示例使用 Foo 和 Bar 类作为占位符,这样你就可以专注于事务的使用,而不需要关注特定的领域模型。对于这个示例来说,DefaultFooService 类在每个已实现的方法体中抛出 UnsupportedOperationException 实例。这种行为让你看到事务被创建然后在响应 UnsupportedOperationException 实例时回滚。以下清单显示了 FooService 接口:
Java
// 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);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
以下示例显示了前述接口的一个实现:
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) {
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
假设 FooService 接口的前两个方法,getFoo(String) 和 getFoo(String, String),必须在具有只读语义的事务上下文中运行,而其他方法,insertFoo(Foo) 和 updateFoo(Foo),必须在具有读写语义的事务上下文中运行。以下配置将在接下来的几段中详细解释:
context.xml:
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
仔细查看前面的配置。它假设你想要将一个服务对象,即 fooService Bean,变为事务性的。要应用的事务语义被封装在 <tx:advice/> 定义中。<tx:advice/> 定义读作 “所有以 get 开头的方法都将在只读事务的上下文中运行,所有其他方法都将以默认的事务语义运行”。<tx:advice/> 标签的 transaction-manager 属性被设置为将驱动事务的 TransactionManager Bean 的名称(在这种情况下,是 txManager Bean)。
Note:如果你想要连接的
TransactionManagerBean 的名称是transactionManager,那么你可以在事务增强(<tx:advice/>)中省略transaction-manager属性。如果你想要连接的TransactionManagerBean 有任何其他名称,你必须显式使用transaction-manager属性,就像前面的示例那样。
<aop:config/> 定义确保由 txAdvice Bean 定义的事务增强在程序的适当点运行。首先,你定义一个切点,该切点匹配 FooService 接口中定义的任何操作(fooServiceOperation)。然后,你使用增强器将切点与 txAdvice 关联。结果表明,在执行 fooServiceOperation 时,由 txAdvice 定义的增强将运行。
在 <aop:pointcut/> 元素中定义的表达式是一个 AspectJ 切点表达式。请参阅 AOP 部分,以获取有关 Spring 中切点表达式的更多详细信息。
一个常见的需求是使整个服务层成为事务性的。做到这一点的最好方法是改变切点表达式,以匹配你的服务层中的任何操作。以下示例显示了如何做到这一点:
XML
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>1
2
3
4
2
3
4
Note:在前面的示例中,假设你的所有服务接口都在
x.y.service包中定义。有关更多详细信息,请参阅 AOP 部分。
现在我们已经分析了配置,你可能会问自己,“这所有的配置实际上做了什么?”
前面显示的配置用于在从 fooService Bean 定义创建的对象周围创建一个事务代理。代理配置了事务增强,因此,当在代理上调用适当的方法时,事务被启动、挂起、标记为只读等,取决于该方法关联的事务配置。考虑以下程序,它测试驱动了前面显示的配置:
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());
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
运行上述程序的输出应如下所示(为了清晰起见,已经截断了 Log4J 的输出和 DefaultFooService 类的 insertFoo(..) 方法抛出的 UnsupportedOperationException 的堆栈跟踪):
Text
<!-- 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)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
要使用响应式事务管理,代码必须使用响应式类型。
Note:Spring 框架使用
ReactiveAdapterRegistry来确定方法返回类型是否为响应式。
下面的清单显示了之前使用的 FooService 的修改版本,但这次代码使用了响应式类型:
Java
// the reactive service interface that we want to make transactional
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);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
以下示例显示了前述接口的实现:
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) {
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
命令式和响应式事务管理在事务边界和事务属性定义上具有相同的语义。命令式和响应式事务的主要区别在于后者的延迟性质。TransactionInterceptor 用事务操作符装饰返回的响应式类型,以开始和清理事务。因此,调用事务响应式方法将实际的事务管理推迟到激活响应式类型处理的订阅类型。
响应式事务管理的另一个方面涉及到数据逃逸,这是编程模型的自然结果。
命令式事务的方法返回值在方法成功终止时从事务方法返回,以便部分计算结果不逃逸出方法闭包。
响应式事务方法返回一个响应式包装类型,该类型表示计算序列以及开始和完成计算的承诺(Promise)。
Publisher 可以在事务进行但未必完成时发出数据。因此,依赖于整个事务成功完成的方法需要确保完成并在调用代码中缓冲结果。
4.3. 回滚声明式事务
上一节概述了如何在应用程序中声明式地为类(通常是服务层类)指定事务设置的基础知识。本节描述了如何在 XML 配置中以简单、声明式的方式控制事务的回滚。有关使用 @Transactional 注解声明式控制回滚语义的详细信息,请参见 @Transactional 设置。
向 Spring 框架的事务基础设施指示一个事务的工作需要回滚的推荐方式是从当前正在事务上下文中执行的代码中抛出一个异常。Spring 框架的事务基础设施代码会捕获任何未处理的异常,因为它在调用堆栈中冒泡上来,并决定是否标记事务进行回滚。
在其默认配置中,Spring 框架的事务基础设施代码只在运行时、未检查的异常的情况下标记事务进行回滚。也就是说,当抛出的异常是 RuntimeException 的实例或子类时。默认情况下,Error 实例也会导致回滚。从事务方法抛出的已检查异常在默认配置中不会导致回滚。
你可以通过指定回滚规则来精确配置哪些异常类型标记事务进行回滚,包括已检查的异常。
Note:回滚规则
回滚规则决定了当抛出给定的异常时,是否应该回滚事务,这些规则是基于模式的。模式可以是一个完全限定的类名,或者是一个异常类型(必须是
Throwable的子类)的完全限定类名的子串,目前不支持通配符。例如,"javax.servlet.ServletException"或"ServletException"的值将匹配javax.servlet.ServletException及其子类。回滚规则可以通过 XML 中的
rollback-for和no-rollback-for属性进行配置,这些属性允许将模式指定为字符串。当使用@Transactional时,可以通过rollbackFor/noRollbackFor和rollbackForClassName/noRollbackForClassName属性配置回滚规则,这些属性分别允许将模式指定为类引用或字符串。当异常类型被指定为类引用时,其完全限定名将被用作模式。因此,@Transactional(rollbackFor = example.CustomException.class)等同于@Transactional(rollbackForClassName = "example.CustomException")。你必须仔细考虑模式的具体性以及是否包含包信息(这不是强制的)。例如,
"Exception"将匹配几乎所有内容,并可能隐藏其他规则。如果"Exception"是用来定义所有已检查异常的规则,那么"java.lang.Exception"就是正确的。对于更独特的异常名,如"BaseBusinessException",可能无需使用异常模式的完全限定类名。此外,回滚规则可能会导致对同名异常和嵌套类的无意识匹配。这是因为,如果抛出的异常的名称包含了为回滚规则配置的异常模式,那么抛出的异常就被认为是给定回滚规则的匹配。例如,给定一个配置为匹配
com.example.CustomException的规则,该规则将匹配到名为com.example.CustomExceptionV2的异常(一个与CustomException在同一包中的异常,但有额外的后缀)或名为com.example.CustomException$AnotherException的异常(一个在CustomException中声明的嵌套类的异常)。
以下 XML 片段演示了如何通过 rollback-for 属性提供异常模式,为已检查的、特定于应用的异常类型配置回滚:
XML
<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>1
2
3
4
5
6
2
3
4
5
6
如果你不希望在抛出异常时回滚事务,你也可以指定 “no rollback” 规则。以下示例告诉 Spring 框架的事务基础设施,即使面临未处理的 InstrumentNotFoundException,也要提交相关的事务:
XML
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>1
2
3
4
5
6
2
3
4
5
6
当 Spring 框架的事务基础设施捕获到一个异常并查阅配置的回滚规则以确定是否标记事务进行回滚时,匹配程度最高的规则将胜出。因此,在以下配置的情况下,除 InstrumentNotFoundException 之外的任何异常都会导致相关事务的回滚:
XML
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>1
2
3
4
5
2
3
4
5
你也可以以编程方式指示需要回滚。尽管这个过程很简单,但它深入地将你的代码与 Spring 框架的事务基础设施紧密耦合。以下示例展示了如何以编程方式指示需要回滚:
Java
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
如果可能的话,强烈建议你使用声明式的回滚方法。如果你确实需要,可以使用编程式回滚,但这种用法与实现干净的 POJO 基础架构的目标相悖。
4.4. 为不同的 Bean 配置不同的事务语义
考虑一种情况,你有一些服务层对象,你希望对每个对象应用完全不同的事务配置。你可以通过定义具有不同切点和 advice-ref 属性值的不同 <aop:advisor/> 元素来实现这一点。
作为比较的一个点,首先假设你的所有服务层类都定义在根包 x.y.service 中。要使该包(或子包)中定义的类的所有 Bean 实例,并且名称以 Service 结尾的 Bean 具有默认的事务配置,你可以编写如下内容:
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
以下示例展示了如何为两个完全不同的 Bean 配置完全不同的事务设置:
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
4.5. <tx:advice/> 设置
本节总结了你可以使用 <tx:advice/> 标签指定的各种事务设置。默认的 <tx:advice/> 设置是:
- 传播设置是
REQUIRED; - 隔离级别是
DEFAULT; - 事务是读写的;
- 事务超时默认为底层事务系统的默认超时,如果不支持超时,则为无;
- 任何
RuntimeException触发回滚,任何已检查的异常都不会;
你可以更改这些默认设置。下表总结了嵌套在 <tx:advice/> 和 <tx:attributes/> 标签中的 <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 | 触发回滚的异常实例的逗号分隔列表。例如,com.foo.MyBusinessException,ServletException | |
no-rollback-for | No | 不触发回滚的异常实例的逗号分隔列表。例如,com.foo.MyBusinessException,ServletException |
<tx:advice/> 设置4.6. 使用 @Transactional
除了基于 XML 的声明式事务配置方法,你还可以使用基于注解的方法。直接在 Java 源代码中声明事务语义,可以让声明更接近受影响的代码。并不存在过度耦合的大问题,因为本来就打算用于事务的代码,几乎总是以这种方式部署的。
Note:标准的
javax.transaction.Transactional注解也被支持作为 Spring 自有注解的替代品。请参考 JTA 1.2 文档以获取更多详情。
通过使用 @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) {
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如上所述,当注解用于类级别时,该注解表示声明类(及其子类)的所有方法的默认值。或者,每个方法都可以单独注解。请参见方法可见性和 @Transactional 以获取有关 Spring 将哪些方法视为事务性的更多详细信息。请注意,类级别的注解不适用于类层次结构中的从祖先类继承的那些方法,在这种情况下,需要在本地重新声明继承的方法,以便参与子类级别的注解。
当像上面这样的 POJO 类在 Spring 上下文中定义为 Bean 时,你可以通过在 @Configuration 类中的 @EnableTransactionManagement 注解使 Bean 实例具有事务性。请参阅 JavaDoc 以获取完整的详细信息。
在 XML 配置中,<tx:annotation-driven/> 标签提供了类似的便利性:
XML
<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Note:如果你想要连接的
TransactionManager的 Bean 名称为transactionManager,则可以省略<tx:annotation-driven/>标签中的transaction-manager属性。如果你想要依赖注入的TransactionManagerBean 的名称为任何其他名称,你必须使用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) {
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
请注意,关于返回的 Publisher,在处理响应式流取消信号方面有特殊的考虑。有关更多详细信息,请参阅 “使用 TransactionalOperator” 下的取消信号部分。
Note:方法可见性和
@Transactional当你在 Spring 的标准配置中使用事务代理时,你应该只将
@Transactional注解应用于具有公共可见性的方法。如果你确实对受保护的、私有的或包可见的方法进行了@Transactional注解,虽然不会引发错误,但被注解的方法并不会展示配置的事务设置。如果你需要对非公共方法进行注解,可以考虑以下段落中针对基于类的代理的提示,或者考虑使用 AspectJ 的编译时或加载时织入(稍后描述)。当在
@Configuration类中使用@EnableTransactionManagement时,通过注册自定义的transactionAttributeSourceBean,也可以为基于类的代理使受保护的或包可见的方法变为事务性的,如下面的示例所示。然而,请注意,基于接口的代理中的事务方法必须始终是公共的,并且定义在被代理的接口中。Java/** * 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); }1
2
3
4
5
6
7
8
9
10
11
12Spring TestContext 框架默认支持非私有的
@Transactional测试方法。请参阅测试章节中的事务管理示例。
你可以将 @Transactional 注解应用于接口定义、接口上的方法、类定义或类上的方法。然而,仅仅存在 @Transactional 注解并不足以激活事务行为。@Transactional 注解只是一些元数据,可以被一些对 @Transactional 意识的运行时基础设施消费,并使用这些元数据来配置具有事务行为的适当的 Bean。在前面的示例中,<tx:annotation-driven/> 元素开启了事务行为。
Warning 1:Spring 团队建议你只对具体的类(和具体类的方法)进行
@Transactional注解,而不是对接口进行注解。你当然可以将@Transactional注解放在接口(或接口方法)上,但这只有在你使用基于接口的代理时才会按照你期望的方式工作。Java 注解不从接口继承的事实意味着,如果你使用基于类的代理(proxy-target-class="true")或基于织入的切面(mode="aspectj"),事务设置不会被代理和织入基础设施识别,对象也不会被包装在事务代理中。
Warning 2:在代理模式下(这是默认的),只有通过代理进来的外部方法调用会被拦截。这意味着自我调用(实际上,目标对象内的一个方法调用目标对象的另一个方法)即使被标记为
@Transactional,也不会在运行时导致实际的事务。此外,代理必须完全初始化才能提供预期的行为,所以你不应该在你的初始化代码中依赖这个特性 —— 例如,在@PostConstruct方法中。
如果你期望自我调用也被包装在事务中,那么请考虑使用 AspectJ 模式(参见下表中的 mode 属性)。在这种情况下,首先没有代理。相反,目标类被织入(也就是,它的字节码被修改)以支持任何类型的方法上的 @Transactional 运行时行为。
| XML 属性 | 注解属性 | 默认值 | 描述 |
|---|---|---|---|
transaction-manager | N/A(请参阅 TransactionManagementConfigurer JavaDoc) | transactionManager | 要使用的事务管理器的名称。只有在事务管理器的名称不是 transactionManager 的情况下才需要,如前面的示例所示 |
mode | mode | proxy | 默认模式(proxy)通过使用 Spring 的 AOP 框架处理被注解的 Bean 以进行代理(遵循代理语义,如前面讨论的,仅适用于通过代理进入的方法调用)。另一种模式(aspectj)则使用 Spring 的 AspectJ 事务切面织入受影响的类,修改目标类字节码以适用于任何类型的方法调用。AspectJ 织入需要在类路径中有 spring-aspects.jar,并且启用了加载时织入(或编译时织入)。(参见 Spring 配置以了解如何设置加载时织入) |
proxy-target-class | proxyTargetClass | false | 仅适用于 proxy 模式。控制为带有 @Transactional 注解的类创建何种类型的事务代理。如果 proxy-target-class 属性设置为 true,则创建基于类的代理。如果 proxy-target-class 为 false 或者省略该属性,则创建标准的基于 JDK 接口的代理。(参见代理机制以仔细了解不同的代理类型) |
order | order | Ordered.LOWEST_PRECEDENCE | 定义应用于带有 @Transactional 注解的 Bean 的事务增强的顺序。(有关 AOP 增强排序规则的更多信息,请参见增强排序。)没有指定排序意味着 AOP 子系统确定增强的顺序 |
Note 1:处理
@Transactional注解的默认增强模式是proxy,这只允许通过代理拦截调用。同一类中的本地调用无法以这种方式被拦截。对于更高级的拦截模式,可以考虑切换到aspectj模式并结合编译时或加载时织入。
Note 2:
proxy-target-class属性控制为带有@Transactional注解的类创建何种类型的事务代理。如果proxy-target-class设置为true,则创建基于类的代理。如果proxy-target-class为false或者省略该属性,则创建标准的基于 JDK 接口的代理。(参见代理机制以讨论不同的代理类型。)
Warning:
@EnableTransactionManagement和<tx:annotation-driven/>只在它们定义的同一应用上下文中寻找@Transactional的 Bean。这意味着,如果你在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) {
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
4.6.1. @Transactional 设置
@Transactional 注解是元数据,它指定了接口、类或方法必须具有事务语义(例如,“当此方法被调用时,启动一个全新的只读事务,暂停任何现有的事务”)。默认的 @Transactional 设置如下:
- 传播设置为
PROPAGATION_REQUIRED; - 隔离级别为
ISOLATION_DEFAULT; - 事务为读写;
- 事务超时默认为底层事务系统的默认超时,如果不支持超时,则为无;
- 任何
RuntimeException或Error触发回滚,任何已检查的异常都不会;
你可以更改这些默认设置。下表总结了 @Transactional 注解的各种属性:
| 属性 | 类型 | 描述 |
|---|---|---|
value | String | 可选的限定符,指定要使用的事务管理器 |
transactionManager | String | value 的别名 |
label | String 数组,用于为事务添加富有表现力的描述 | 事务管理器可能会评估标签,以将实现特定的行为与实际事务关联起来 |
propagation | 枚举:Propagation | 可选的传播设置 |
isolation | 枚举:Isolation | 可选的隔离级别。仅适用于 REQUIRED 或 REQUIRES_NEW 的传播值 |
timeout | int(以秒为单位) | 可选的事务超时。仅适用于 REQUIRED 或 REQUIRES_NEW 的传播值 |
timeoutString | String(以秒为单位) | 以 String 值指定 timeout 秒数的替代方法 —— 例如,作为占位符 |
readOnly | boolean | 读写事务与只读事务。仅适用于 REQUIRED 或 REQUIRES_NEW 的值 |
rollbackFor | 必须从 Throwable 派生的 Class 对象数组 | 必须导致回滚的异常类型的可选数组 |
rollbackForClassName | 异常名称模式的数组 | 必须导致回滚的异常名称模式的可选数组 |
noRollbackFor | 必须从 Throwable 派生的 Class 对象数组 | 不能导致回滚的异常类型的可选数组 |
noRollbackForClassName | 异常名称模式的数组 | 不能导致回滚的异常名称模式的可选数组 |
@Transactional 设置Note:请参阅回滚规则以获取有关回滚规则语义、模式以及可能的无意匹配警告的更多详细信息。
目前,你无法明确控制事务的名称,其中 “名称” 指的是在适用的情况下出现在事务监视器(例如,WebLogic 的事务监视器)和日志输出中的事务名称。对于声明式事务,事务名称始终是事务增强类的完全限定类名 + . + 方法名。例如,如果 BusinessService 类的 handlePayment(..) 方法启动了一个事务,那么事务的名称将是:com.example.BusinessService.handlePayment。
4.6.2. 使用 @Transactional 的多个事务管理器
大多数 Spring 应用只需要一个事务管理器,但在某些情况下,你可能希望在一个应用中使用多个独立的事务管理器。你可以使用 @Transactional 注解的 value 或 transactionManager 属性来可选地指定要使用的 TransactionManager 的标识。这可以是事务管理器的 Bean 名称或者 Bean 的限定符值。例如,使用限定符表示法,你可以将以下的 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() { ... }
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
下面的清单显示了 Bean 声明:
XML
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
...
<qualifier value="account"/>
</bean>
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
在这种情况下,TransactionalService 上的各个方法在不同的事务管理器下运行,这些事务管理器由 order、account 和 reactive-account 限定符进行区分。如果没有找到特定的有限定符的 TransactionManager Bean,那么 <tx:annotation-driven> 默认的目标 Bean 名称 transactionManager 仍然会被使用。
4.6.3. 自定义组合注解
如果你发现在许多不同的方法上反复使用 @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 {
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
前面的注解让我们可以按照下面的方式编写上一节中的示例:
Java
public class TransactionalService {
@OrderTx
public void setSomething(String name) {
// ...
}
@AccountTx
public void doSomething() {
// ...
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
在前面的示例中,我们使用语法来定义事务管理器限定符和事务标签,但我们也可以包括传播行为、回滚规则、超时和其他特性。
4.7. 事务传播
这一节描述了 Spring 中事务传播的一些语义。请注意,这一节并不是事务传播的适当介绍。相反,它详细说明了 Spring 中关于事务传播的一些语义。
在 Spring 管理的事务中,要注意物理事务和逻辑事务之间的区别,以及传播设置如何应用于这种区别。
4.7.1. 理解 PROPAGATION_REQUIRED

PROPAGATION_REQUIREDPROPAGATION_REQUIRED 强制执行物理事务,如果当前作用域还没有事务,则在本地执行,或者参与到为更大作用域定义的现有 “外部” 事务中。这在同一线程内的常见调用堆栈安排中是一个很好的默认值(例如,一个服务门面委托给几个仓库方法,其中所有底层资源都必须参与到服务级别的事务中)。
Tip:默认情况下,参与的事务加入外部作用域的特性,静默忽略本地隔离级别、超时值或只读标志(如果有)。如果你希望在参与到具有不同隔离级别的现有事务时拒绝隔离级别声明,那么请考虑在你的事务管理器上将
validateExistingTransactions标志切换为true。这种非宽松模式也拒绝只读不匹配(即,试图参与到只读外部作用域的内部读写事务)。
当传播设置为 PROPAGATION_REQUIRED 时,每个应用此设置的方法都会创建一个逻辑事务范围。每个这样的逻辑事务范围都可以单独确定仅回滚状态,其中外部事务范围在逻辑上独立于内部事务范围。在标准的 PROPAGATION_REQUIRED 行为情况下,所有这些范围都映射到同一物理事务。因此,内部事务范围设置的仅回滚标记确实会影响外部事务实际提交的机会。
然而,在内部事务范围设置仅回滚标记的情况下,外部事务本身尚未决定回滚,因此由内部事务范围静默触发的回滚是意料之外的。在这一点上,会抛出相应的 UnexpectedRollbackException。这是预期的行为,以便事务的调用者永远不会被误导以为已经执行了提交,而实际上并没有。因此,如果一个内部事务(外部调用者并不知道)静默地将事务标记为仅回滚,外部调用者仍然调用提交。外部调用者需要接收到一个 UnexpectedRollbackException,明确地指出实际上执行的是回滚,而不是提交。
4.7.2. 理解 PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRES_NEW与 PROPAGATION_REQUIRED 相比,PROPAGATION_REQUIRES_NEW 总是为每个受影响的事务范围使用独立的物理事务,永远不会参与外部范围的现有事务。在这样的安排中,底层资源事务是不同的,因此,可以独立提交或回滚,外部事务不会受到内部事务回滚状态的影响,内部事务的锁在其完成后立即释放。这样的独立内部事务也可以声明其自己的隔离级别、超时和只读设置,而不继承外部事务的特性。
Tip:附加到外部事务的资源将保持绑定,而内部事务将获取其自己的资源,如新的数据库连接。这可能会导致连接池耗尽,并可能导致死锁,如果多个线程有一个活动的外部事务并等待为其内部事务获取新的连接,而连接池无法再分配任何这样的内部连接。除非你的连接池大小适当,超过并发线程数至少 1,否则不要使用
PROPAGATION_REQUIRES_NEW。
4.7.3. 理解 PROPAGATION_NESTED
PROPAGATION_NESTED 使用单个物理事务,该事务具有多个可以回滚到的保存点。这样的部分回滚允许内部事务范围触发其范围的回滚,尽管一些操作已经回滚,但外部事务仍能继续物理事务。此设置通常映射到 JDBC 保存点,因此只适用于 JDBC 资源事务。请参阅 Spring 的 DataSourceTransactionManager。
4.8. 增强事务操作
假设你想要同时运行事务操作和一些基本的性能分析增强。你如何在 <tx:annotation-driven/> 的上下文中实现这个效果?
当你调用 updateFoo(Foo) 方法时,你希望看到以下的动作:
- 配置的性能分析切面开始;
- 事务增强运行;
- 被增强对象上的方法运行;
- 事务提交;
- 性能分析切面报告整个事务方法调用的准确持续时间;
Note:本章并不关注详细解释 AOP(除非它适用于事务)。请参阅 AOP 以获取 AOP 配置和 AOP 一般的详细内容。
下面的代码展示了前面讨论的简单性能分析切面:
Java
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
增强的顺序是通过 Ordered 接口来控制的。关于增强排序的完整细节,请参阅增强排序。
以下配置创建了一个 fooService Bean,它按照期望的顺序应用了性能分析和事务切面:
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
你可以以类似的方式配置任意数量的额外切面。
以下示例创建了与前两个示例相同的设置,但使用了纯 XML 声明式方法:
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>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
前述配置的结果是一个 fooService Bean,它按照该顺序应用了性能分析和事务切面。如果你希望在进入时,性能分析增强在事务增强之后运行,并且在退出时,在事务增强之前运行,你可以交换性能分析切面 Bean 的 order 属性的值,使其高于事务增强的 order 值。
你可以以类似的方式配置额外的切面。
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 容器之外运行的应用程序,所以我们将向你展示如何以编程方式进行操作。
Note:在继续之前,你可能想阅读使用
@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);1
2
3
4
5
2
3
4
5
Tip:当你使用这个切面时,你必须对实现类(或该类中的方法或两者都有)进行注解,而不是该类实现的接口(如果有的话)。AspectJ 遵循 Java 的规则,即接口上的注解不会被继承。
类上的 @Transactional 注解指定了该类中任何公共方法执行的默认事务语义。
类内部方法上的 @Transactional 注解会覆盖类注解(如果存在)给出的默认事务语义。你可以对任何方法进行注解,无论其可见性如何。
要将你的应用程序与 AnnotationTransactionAspect 织入,你必须使用 AspectJ 构建你的应用程序(参见 AspectJ 开发指南)或使用加载时织入。请参阅 Spring 框架中的 AspectJ 加载时织入以讨论 AspectJ 的加载时织入。
5. 编程式事务管理
Spring 框架提供了两种编程式事务管理的方法,分别是:
- 使用
TransactionTemplate或TransactionalOperator; - 直接使用
TransactionManager实现;
Spring 团队通常推荐在命令式流程中使用 TransactionTemplate 进行编程式事务管理,而在响应式代码中使用 TransactionalOperator。第二种方法类似于使用 JTA 的 UserTransaction API,尽管异常处理不那么麻烦。
5.1. 使用 TransactionTemplate
TransactionTemplate 采用了与 Spring 的其他模板(如 JdbcTemplate)相同的方法。它使用回调方法(使应用程序代码无需进行样板式的获取和释放事务资源),并导致代码是意图驱动的,也就是说,你的代码只专注于你想要做的事情。
Note:如接下来的示例所示,使用
TransactionTemplate将你完全绑定到 Spring 的事务基础设施和 API。编程式事务管理是否适合你的开发需求,是你自己必须做出的决定。
必须在事务上下文中运行并显式使用 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();
}
});
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果没有返回值,你可以使用方便的 TransactionCallbackWithoutResult 类与匿名类,如下所示:
Java
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});1
2
3
4
5
6
2
3
4
5
6
回调内的代码可以通过在提供的 TransactionStatus 对象上调用 setRollbackOnly() 方法来回滚事务,如下所示:
Java
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessException ex) {
status.setRollbackOnly();
}
}
});1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
5.1.1. 指定事务设置
你可以在 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...
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
以下示例使用 Spring XML 配置定义了一个具有一些自定义事务设置的 TransactionTemplate:
XML
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>1
2
3
4
5
2
3
4
5
然后,你可以将 sharedTransactionTemplate 注入到需要的尽可能多的服务中。
最后,TransactionTemplate 类的实例是线程安全的,实例不保持任何会话状态。然而,TransactionTemplate 实例确实保持配置状态。因此,虽然许多类可能共享一个 TransactionTemplate 实例,但如果一个类需要使用具有不同设置的 TransactionTemplate(例如,不同的隔离级别),你需要创建两个不同的 TransactionTemplate 实例。
5.2. 使用 TransactionalOperator
TransactionalOperator 遵循与其他响应式操作符类似的操作符设计。它使用回调方法(使应用程序代码无需进行样板式的获取和释放事务资源),并导致代码是意图驱动的,也就是说,你的代码只专注于你想要做的事情。
Note:如接下来的示例所示,使用
TransactionalOperator将你完全绑定到 Spring 的事务基础设施和 API。编程式事务管理是否适合你的开发需求,是你自己必须做出的决定。
必须在事务上下文中运行并显式使用 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);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
}
}
});1
2
3
4
5
6
7
2
3
4
5
6
7
5.2.1. 取消信号
在 Reactive Streams 中,Subscriber 可以取消其 Subscription 并停止其 Publisher。Project Reactor 的操作符,以及其他库(如 next()、take(long)、timeout(Duration) 等)都可以发出取消操作。无法知道取消的原因,无论是由于错误还是简单地对进一步消费失去兴趣。自 5.3 版本以来,取消信号会导致回滚。因此,考虑从事务 Publisher 下游使用的操作符是很重要的。特别是在 Flux 或其他多值 Publisher 的情况下,必须消费完整的输出以允许事务完成。
5.2.2. 指定事务设置
你可以为 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);
}
}5.3. 使用 TransactionManager
以下部分将解释命令式和响应式事务管理器的编程使用方法。
5.3.1. 使用 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);1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
5.3.2. 使用 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
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
6. 在编程式和声明式事务管理之间进行选择
编程式事务管理通常只在你有少量的事务操作时才是一个好主意。例如,如果你有一个网络应用程序,只需要对某些更新操作进行事务处理,你可能不希望使用 Spring 或任何其他技术来设置事务代理。在这种情况下,使用 TransactionTemplate 可能是一个好方法。只有通过编程式事务管理方法,才能显式地设置事务名称。
另一方面,如果你的应用程序有大量的事务操作,声明式事务管理通常是值得的。它将事务管理从业务逻辑中剥离出来,并且配置起来并不困难。当使用 Spring 框架,而不是 EJB CMT 时,声明式事务管理的配置成本大大降低。
7. 事务绑定事件
从 Spring 4.2 开始,事件的监听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成时处理事件。这样做可以让事件在当前事务的结果对监听器实际有意义时,以更灵活的方式使用。
你可以使用 @EventListener 注解注册一个常规的事件监听器。如果你需要将其绑定到事务,使用 @TransactionalEventListener。这样做时,默认情况下,监听器会绑定到事务的提交阶段。
下一个示例展示了这个概念。假设一个组件发布了一个订单创建事件,我们想要定义一个监听器,只有在发布它的事务成功提交后,才处理该事件。以下示例设置了这样一个事件监听器:
Java
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
// ...
}
}1
2
3
4
5
6
7
2
3
4
5
6
7
@TransactionalEventListener 注解公开了一个 phase 属性,允许你自定义监听器应绑定到事务的哪个阶段。有效的阶段有 BEFORE_COMMIT、AFTER_COMMIT(默认)、AFTER_ROLLBACK,以及 AFTER_COMPLETION,它聚集了事务完成(无论是提交还是回滚)。
如果没有事务正在运行,监听器根本不会被调用,因为我们无法满足所需的语义。然而,你可以通过将注解的 fallbackExecution 属性设置为 true 来覆盖该行为。
Tip:
@TransactionalEventListener只适用于由PlatformTransactionManager管理的线程绑定事务。由ReactiveTransactionManager管理的响应式事务使用 Reactor 上下文而不是线程局部属性,所以从事件监听器的角度看,没有与其兼容的活动事务可以参与。
8. 特定应用服务器的集成
Spring 的事务抽象通常与应用服务器无关。此外,Spring 的 JtaTransactionManager 类(可以选择性地执行 JTA UserTransaction 和 TransactionManager 对象的 JNDI 查找)会自动检测后者对象的位置,这个位置因应用服务器而异。访问 JTA TransactionManager 允许增强事务语义,特别是支持事务挂起。详情请参阅 JtaTransactionManager JavaDoc。
Spring 的 JtaTransactionManager 是在 Java EE 应用服务器上运行的标准选择,已知可以在所有常见服务器上工作。许多服务器(包括 GlassFish、JBoss 和 Geronimo)也支持高级功能,如事务挂起,无需任何特殊配置。然而,对于完全支持的事务挂起和进一步的高级集成,Spring 包含了针对 WebLogic Server 和 WebSphere 的特殊适配器。这些适配器将在以下部分进行讨论。
对于标准场景,包括 WebLogic Server 和 WebSphere,可以考虑使用方便的 <tx:jta-transaction-manager/> 配置元素。当配置时,此元素会自动检测底层服务器并选择平台上可用的最佳事务管理器。这意味着你不需要显式配置特定于服务器的适配器类(如在以下部分中所讨论的)。相反,它们会自动选择,标准的 JtaTransactionManager 是默认的后备选项。
8.1. IBM WebSphere
在 WebSphere 6.1.0.9 及以上版本中,推荐使用的 Spring JTA 事务管理器是 WebSphereUowTransactionManager。这个特殊的适配器使用了 IBM 的 UOWManager API,该 API 在 WebSphere Application Server 6.1.0.9 及以后的版本中可用。使用这个适配器,由 Spring 驱动的事务挂起(由 PROPAGATION_REQUIRES_NEW 初始化的挂起和恢复)得到了 IBM 的官方支持。
8.2. Oracle WebLogic Server
在 WebLogic Server 9.0 或以上版本中,你通常会使用 WebLogicJtaTransactionManager 而不是库存的 JtaTransactionManager 类。这个特殊的针对 WebLogic 的 JtaTransactionManager 子类,在 WebLogic 管理的事务环境中,支持 Spring 的事务定义的全部功能,超越了标准的 JTA 语义。功能包括事务名称、每个事务的隔离级别,以及在所有情况下正确地恢复事务。
9. 常见问题的解决方案
本节介绍一些常见问题的解决方案。
9.1. 对特定的 DataSource 使用错误的事务管理器
根据你选择的事务技术和需求,使用正确的 PlatformTransactionManager 实现。如果正确使用,Spring 框架只提供了一个简单直接且可移植的抽象。如果你使用全局事务,你必须对所有的事务操作使用 org.springframework.transaction.jta.JtaTransactionManager 类(或其特定于应用服务器的子类)。否则,事务基础设施会尝试在诸如容器 DataSource 实例之类的资源上执行本地事务。这样的本地事务没有意义,一个好的应用服务器会将它们视为错误。
10. 更多资源
如果你想了解更多关于 Spring 框架的事务支持,可以参考以下资源:
《Spring 中的分布式事务,有 XA 和无 XA》是 JavaWorld 的一场演讲,由 Spring 的 David Syer 指导你通过七种分布式事务模式,其中三种使用 XA,四种不使用。
《Java 事务设计策略》是一本可从 InfoQ 获取的书籍,它提供了一个节奏适中的 Java 事务介绍。它还包括如何配置和使用 Spring 框架和 EJB3 的事务的并排示例。