理解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。像Spring框架IoC容器中的任何其他对象(或bean)一样定义PlatformTransactionManager实现。即使在使用JTA时,这一优点也使Spring框架事务成为一种有价值的抽象。您可以比直接使用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框架或任何事务管理解决方案至关重要。

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的定义使用了<jndi-lookup/>标签来自jee命名空间。有关更多信息,请参阅JEE模式
如果您使用JTA,则无论您使用何种数据访问技术,如JDBC、Hibernate JPA或任何其他支持的技术,您的事务管理器定义应该是相同的。这是因为JTA事务是全局事务,可以注册任何事务资源。

在所有Spring事务设置中,应用程序代码无需更改。您可以仅通过更改配置来更改事务管理方式,即使这种更改意味着从本地事务到全局事务或反之。

Hibernate事务设置

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

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

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

在这种情况下,txManager bean是HibernateTransactionManager类型。与DataSourceTransactionManager需要引用DataSource类似,HibernateTransactionManager需要引用SessionFactory。以下示例声明了sessionFactorytxManager beans:

<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示例中使用相同的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"/>