对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

理解 Spring 框架的事务抽象

Spring 事务抽象的关键在于事务策略(transaction strategy)的概念。事务策略由 TransactionManager 定义,具体而言,命令式事务管理使用 org.springframework.transaction.PlatformTransactionManager 接口,而响应式事务管理则使用 org.springframework.transaction.ReactiveTransactionManager 接口。以下代码清单展示了 PlatformTransactionManager API 的定义:spring-doc.cadn.net.cn

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 Framework 的事务抽象非常有价值,即使你在使用 JTA 时也是如此。与直接使用 JTA 相比,你可以更加轻松地测试事务性代码。spring-doc.cadn.net.cn

同样,秉承 Spring 的理念,任何 PlatformTransactionManager 接口的方法可能抛出的 TransactionException 都是非受检异常(即它继承自 java.lang.RuntimeException 类)。事务基础设施故障几乎总是致命的。在极少数情况下,如果应用程序代码确实能够从事务失败中恢复,应用程序开发人员仍然可以选择捕获并处理 TransactionException。关键在于,开发人员并非被迫这样做。spring-doc.cadn.net.cn

getTransaction(..) 方法根据一个 TransactionStatus 参数返回一个 TransactionDefinition 对象。所返回的 TransactionStatus 可能表示一个新事务,也可能表示一个现有事务(如果当前调用栈中已存在匹配的事务)。后一种情况意味着,与 Jakarta EE 事务上下文类似,TransactionStatus 与执行线程相关联。spring-doc.cadn.net.cn

从 Spring Framework 5.2 开始,Spring 还为使用响应式类型或 Kotlin 协程的响应式应用程序提供了事务管理抽象。以下代码清单展示了由 org.springframework.transaction.ReactiveTransactionManager 定义的事务策略:spring-doc.cadn.net.cn

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)。spring-doc.cadn.net.cn

The TransactionDefinition 接口规定了:spring-doc.cadn.net.cn

  • 传播行为(Propagation):通常,事务范围内的所有代码都在该事务中运行。然而,当一个带有事务的方法在已有事务上下文中执行时,你可以指定其行为。例如,代码可以继续在现有事务中运行(这是常见情况),也可以挂起现有事务并创建一个新事务。Spring 提供了所有 EJB CMT 中熟悉的事务传播选项。有关 Spring 中事务传播语义的详细说明,请参阅事务传播spring-doc.cadn.net.cn

  • 隔离:此事务与其他事务工作的隔离程度。例如,此事务是否能够看到其他事务的未提交写入?spring-doc.cadn.net.cn

  • 超时:此事务在自动回滚之前运行的最长时间,由底层事务基础设施进行控制。spring-doc.cadn.net.cn

  • 读只状态:当您的代码仅读取而不修改数据时,可以使用只读事务。在某些情况下,例如使用Hibernate时,只读事务可以是一种有用的优化手段。spring-doc.cadn.net.cn

这些设置体现了标准的事务概念。如有必要,请参考讨论事务隔离级别及其他核心事务概念的相关资料。 理解这些概念对于使用 Spring 框架或任何事务管理解决方案都至关重要。spring-doc.cadn.net.cn

TransactionStatus 接口为事务性代码提供了一种简单的方式来控制事务执行并查询事务状态。这些概念应该是熟悉的,因为它们在所有事务 API 中都很常见。以下代码清单展示了 TransactionStatus 接口:spring-doc.cadn.net.cn

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 实现都至关重要。 你通常通过依赖注入来定义该实现。spring-doc.cadn.net.cn

TransactionManager 的实现通常需要了解其运行环境:JDBC、JTA、Hibernate 等。以下示例展示了如何定义一个本地的 PlatformTransactionManager 实现(在本例中,使用的是纯 JDBC)。spring-doc.cadn.net.cn

您可以定义一个JDBC DataSource,通过创建类似于以下的bean:spring-doc.cadn.net.cn

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

The related PlatformTransactionManager bean definition then has a reference to the DataSource definition. 它应该类似于以下示例:spring-doc.cadn.net.cn

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

如果你在 Jakarta EE 容器中使用 JTA,那么你需要结合 Spring 的 DataSource,通过 JNDI 获取容器提供的 JtaTransactionManager。以下示例展示了 JTA 和 JNDI 查找版本的配置方式:spring-doc.cadn.net.cn

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

The JtaTransactionManager 不需要知道关于 DataSource(或其他任何特定资源)的信息,因为它使用了容器的全局事务管理基础设施。spring-doc.cadn.net.cn

前面的 dataSource bean 定义使用了 <jndi-lookup/> 标签,该标签来自 jee 命名空间。更多信息请参见 JEE Schema
如果你使用 JTA,无论你采用何种数据访问技术(无论是 JDBC、Hibernate JPA,还是任何其他受支持的技术),你的事务管理器定义都应保持一致。这是因为 JTA 事务是全局事务,可以纳入任何事务性资源。

在所有 Spring 事务配置中,应用程序代码无需更改。您只需修改配置即可改变事务的管理方式,即使这种更改意味着从本地事务切换到全局事务,或反之亦然。spring-doc.cadn.net.cn

Hibernate 事务设置

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

The DataSource bean定义类似于前面示例中所示的本地JDBC示例, 因此在以下示例中不予展示。spring-doc.cadn.net.cn

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

在此情况下,txManager bean 的类型为 HibernateTransactionManager。与 DataSourceTransactionManager 需要一个对 DataSource 的引用类似,HibernateTransactionManager 也需要一个对 SessionFactory 的引用。以下示例声明了 sessionFactorytxManager 这两个 bean:spring-doc.cadn.net.cn

<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 release mode)配置,使其感知到 JTA:spring-doc.cadn.net.cn

<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,以应用相同的默认设置:spring-doc.cadn.net.cn

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