Understanding the Spring Framework 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),尽管你可以从应用程序代码中programmatically使用它。由于`PlatformTransactionManager`是一个接口,因此可以根据需要轻松地模拟或存根。它不与查找策略(例如JNDI)绑定。PlatformTransactionManager`实现被定义为Spring Framework IoC容器中的任何其他对象(或bean)。仅此优点就使Spring Framework事务成为一项有价值的抽象,即使在你使用JTA时也是如此。你可以比直接使用JTA更轻松地测试事务代码。 同样,根据 Spring 的理念,`TransactionException 可能被任何 TransactionManager 接口的方法抛出,它未被签入(也就是,它扩展了 java.lang.RuntimeException 类)。事务基础设施故障几乎都是致命的。在应用程序代码可以实际从事务故障中恢复的罕见情况下,应用程序开发人员仍然可以选择来捕获并处理 TransactionException。关键点是开发人员并非_被迫_去做这件事。 getTransaction(..) 方法返回一个 TransactionStatus 对象,具体取决于一个 TransactionDefinition 参数。返回的 TransactionStatus 可能代表一个新的事务,也可以代表一个现有的事务(如果在当前调用栈中存在一个匹配的事务)。在后一种情况下的含义是,与 Jakarta EE 事务上下文一样,一个 TransactionStatus 与一个执行线程相关联。 从 Spring Framework 5.2 开始,Spring 还为使用响应式类型或 Kotlin Coroutine 的响应式应用程序提供了一个事务管理抽象。以下列表展示了由 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),尽管你可以从应用程序代码中programmatically使用它。由于`ReactiveTransactionManager`是一个接口,因此可以根据需要轻松地模拟或存根。 TransactionDefinition 接口规定:

  • 传播:通常,事务范围内的所有代码都运行在该事务中。但是,如果在事务上下文已存在的情况下运行事务方法,您可以指定行为。例如,代码可以在现有事务中继续运行(常见情况),或者可以挂起现有事务并创建一个新事务。Spring 提供了 EJB CMT 所有熟悉的事务传播选项。若要了解 Spring 中事务传播的语义,请参阅 Transaction Propagation

  • 隔离:事务与其他事务的工作隔离的程度。例如,此事务是否可以看到其他事务未提交的写入?

  • 超时:此事务在超时且被底层事务基础架构自动回滚之前运行的时间。

  • 只读状态:当代码读取而不修改数据时,可以使用只读事务。在某些情况下,只读事务可能是一个有用的优化,例如在使用 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,那么可以使用容器 DataSource,通过 JNDI 获取,结合 Spring 的 JtaTransactionManager 一同使用。以下示例展示了 JTA 和 JNDI 的查找版本:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		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(或任何其他特定资源),因为它使用容器的全局事务管理基础设施。

dataSource Bean 的前置定义使用 jee 命名空间中的 <jndi-lookup/> 标记。有关更多信息,请参阅 The JEE Schema

如果您使用 JTA,则事务管理器定义应一致,无论您使用哪种数据访问技术,无论是 JDBC、Hibernate JPA 还是任何其他受支持的技术。这是因为 JTA 事务是全局事务,可以列出任何事务性资源。

在所有 Spring 事务设置中,应用程序代码无需更改。你可以通过简单地更改配置来更改事务的管理方式,即使更改意味着从本地事务移动到全局事务,反之亦然。

Hibernate Transaction Setup

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

DataSource Bean 定义与前面所示的本地 JDBC 示例类似,因此以下示例中不会显示它。

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

在这种情况下,txManager Bean 是 HibernateTransactionManager 类型。正如 DataSourceTransactionManager 需要引用 DataSource 一样,HibernateTransactionManager 需要引用 SessionFactory。以下示例声明了 sessionFactorytxManager 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 了解 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"/>