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
(或任何其他特定资源),因为它使用容器的全局事务管理基础设施。
|
如果您使用 JTA,则事务管理器定义应一致,无论您使用哪种数据访问技术,无论是 JDBC、Hibernate JPA 还是任何其他受支持的技术。这是因为 JTA 事务是全局事务,可以列出任何事务性资源。 |
在所有 Spring 事务设置中,应用程序代码无需更改。你可以通过简单地更改配置来更改事务的管理方式,即使更改意味着从本地事务移动到全局事务,反之亦然。
Hibernate Transaction Setup
你还可以轻松使用 Hibernate 本地事务,如以下示例所示。在这种情况下,你需要定义一个 Hibernate LocalSessionFactoryBean
,你的应用程序代码可以使用它来获取 Hibernate Session
实例。
DataSource
Bean 定义与前面所示的本地 JDBC 示例类似,因此以下示例中不会显示它。
如果 |
在这种情况下,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"/>