理解 Spring Framework 事务抽象
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 Framework IoC 容器中的任何其他对象(或 bean)一样定义。仅凭这一优点就使得 Spring Framework 事务成为一个有价值的抽象,即使在使用 JTA 时也是如此。与直接使用 JTA 相比,你可以更容易地测试事务代码。
再次,与 Spring 的理念保持一致,PlatformTransactionManager 接口的任何方法可能抛出的 TransactionException 都是非检查异常(即,它继承自 java.lang.RuntimeException 类)。事务基础设施故障几乎总是致命的。在应用代码确实可以从事务故障中恢复的极少数情况下,应用开发者仍然可以选择捕获并处理 TransactionException。关键点在于开发者不是被迫这样做的。
getTransaction(..) 方法根据 TransactionDefinition 参数返回一个 TransactionStatus 对象。如果当前调用栈中存在匹配的事务,返回的 TransactionStatus 可能表示一个新事务,或者表示一个现有事务。后者的含义是,与 Jakarta EE 事务上下文一样,TransactionStatus 与执行线程相关联。
Spring 还为使用响应式类型或 Kotlin Coroutines 的响应式应用提供了事务管理抽象。以下列表展示了由 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 接口指定了
-
传播 (Propagation):通常,事务范围内的所有代码都在该事务中运行。但是,如果在一个事务上下文已存在时运行一个事务方法,你可以指定其行为。例如,代码可以继续在现有事务中运行(常见情况),或者现有事务可以被挂起并创建新事务。Spring 提供了 EJB CMT 中熟悉的所有事务传播选项。要了解 Spring 中事务传播的语义,请参阅事务传播。
-
隔离 (Isolation):此事务与其他事务的工作隔离的程度。例如,此事务能否看到其他事务未提交的写入?
-
超时 (Timeout):此事务在超时并被底层事务基础设施自动回滚之前运行的时间长度。
-
只读状态 (Read-only status):当你的代码只读而不修改数据时,可以使用只读事务。只读事务在某些情况下可能是一个有用的优化,例如使用 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 Schema。 |
| 如果你使用 JTA,无论你使用何种数据访问技术(无论是 JDBC、Hibernate JPA 还是任何其他支持的技术),你的事务管理器定义都应该相同。这是因为 JTA 事务是全局事务,可以登记任何事务资源。 |
在所有 Spring 事务设置中,应用代码无需更改。你只需更改配置即可更改事务的管理方式,即使这种更改意味着从本地事务迁移到全局事务,反之亦然。
Hibernate 事务设置
你还可以轻松使用 Hibernate 本地事务,如下例所示。在这种情况下,你需要定义一个 Hibernate LocalSessionFactoryBean,你的应用代码可以使用它来获取 Hibernate Session 实例。
DataSource bean 定义类似于之前显示的本地 JDBC 示例,因此在以下示例中未显示。
如果 DataSource(由任何非 JTA 事务管理器使用)通过 JNDI 查找并由 Jakarta EE 容器管理,它应该是非事务性的,因为事务是由 Spring Framework(而不是 Jakarta EE 容器)管理的。 |
在这种情况下,txManager bean 的类型是 HibernateTransactionManager。与 DataSourceTransactionManager 需要引用 DataSource 一样,HibernateTransactionManager 需要引用 SessionFactory。以下示例声明了 sessionFactory 和 txManager 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"/>