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

掌握Spring框架事务抽象层详解

Spring事务抽象的关键在于事务策略的概念。事务策略由一个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 是一个接口,可以根据需要轻松地进行模拟或存根。 它不依赖于查找策略,例如 JNDI。 PlatformTransactionManager 实现方式与其他对象(或 bean)在 Spring Framework IoC 容器中的定义方式相同。仅此优势就使 Spring Framework 的事务成为一种值得的抽象,即使您使用 JTA 时也是如此。您可以比直接使用 JTA 更容易地测试事务代码。spring-doc.cadn.net.cn

再次遵循Spring的哲学,TransactionException可以由PlatformTransactionManager接口的任何方法抛出,它是未检查的(即,它扩展了java.lang.RuntimeException类)。事务基础设施故障通常是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复时,应用程序开发人员仍然可以选择捕获并处理TransactionException。关键点是,开发者并不被强制这样做。spring-doc.cadn.net.cn

getTransaction(..)方法根据TransactionDefinition参数返回一个TransactionStatus对象。返回的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 是一个接口,可以根据需要轻松地进行模拟或存根。spring-doc.cadn.net.cn

TransactionDefinition 接口指定:spring-doc.cadn.net.cn

  • 传播:通常,事务范围内的所有代码都在该事务中运行。但是,您可以指定当已存在事务上下文时,运行事务方法的行为。例如,代码可以继续在现有事务中运行(常见的情况),或者可以挂起现有事务并创建新事务。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

您可以通过创建类似以下内容的bean来定义一个JDBC DataSourcespring-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>

相关的 PlatformTransactionManager bean 定义随后引用了 DataSource 定义。它应该类似于以下示例:spring-doc.cadn.net.cn

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

如果您在Jakarta EE容器中使用JTA,那么您会通过JNDI获取容器DataSource,并与Spring的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>

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

前面定义的 dataSource bean 使用了 <jndi-lookup/> 标签 来自 jee 命名空间。有关更多信息,请参阅 JEE 模式
如果使用JTA,您的事务管理器定义应该相同,无论您使用的是哪种数据访问技术,无论是JDBC、Hibernate JPA,还是任何其他支持的技术。这是由于JTA事务是全局事务,可以加入任何事务性资源。

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

Hibernate事务设置

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

DataSource bean 定义与前面显示的本地 JDBC 示例类似 因此,在下面的示例中未显示。spring-doc.cadn.net.cn

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

这个情况下的 txManager bean 是 HibernateTransactionManager 类型。与 DataSourceTransactionManager 需要对 DataSource 的引用一样,HibernateTransactionManager 也需要对 SessionFactory 的引用。下面的示例声明了 sessionFactorytxManager beans: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感知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"/>