理解 Spring 框架事务抽象

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 是一个接口,所以它可以根据需要轻松地被模拟或存根。它不依赖于查找策略,例如 JNDI。PlatformTransactionManager 实现像 Spring 框架 IoC 容器中的任何其他对象(或 bean)一样定义。仅此一项优势就使 Spring 框架事务成为一个值得的抽象,即使您使用 JTA。您可以比直接使用 JTA 更轻松地测试事务代码。

同样,为了保持 Spring 的理念,PlatformTransactionManager 接口的任何方法都可能抛出的 TransactionException 是未检查的(即,它扩展了 java.lang.RuntimeException 类)。事务基础设施故障几乎总是致命的。在应用程序代码实际上可以从事务故障中恢复的极少数情况下,应用程序开发人员仍然可以选择捕获和处理 TransactionException。关键点是开发人员没有被强制这样做。

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

从 Spring Framework 5.2 开始,Spring 还为使用响应式类型或 Kotlin 协程的响应式应用程序提供事务管理抽象。以下清单显示了由 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 是一个接口,所以可以根据需要轻松地模拟或存根它。

TransactionDefinition 接口指定

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

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

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

  • 只读状态:当您的代码读取数据但不修改数据时,您可以使用只读事务。在某些情况下,只读事务可能是一种有用的优化,例如当您使用 Hibernate 时。

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

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 查找版本的样子

<?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/> 标签。有关更多信息,请参见 JEE 架构
如果您使用 JTA,无论您使用什么数据访问技术,无论是 JDBC、Hibernate JPA 还是任何其他支持的技术,您的事务管理器定义都应该看起来一样。这是因为 JTA 事务是全局事务,可以注册任何事务性资源。

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

Hibernate 事务设置

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

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

如果 DataSource(由任何非 JTA 事务管理器使用)通过 JNDI 查找并由 Jakarta EE 容器管理,它应该是非事务性的,因为 Spring 框架(而不是 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"/>