理解Spring框架的事务抽象
Spring事务抽象的关键在于“事务策略”(transaction strategy)的概念。事务策略由TransactionManager来定义,具体来说,对于命令式事务管理(imperative transaction management),使用的是org.springframework.transaction.PlatformTransactionManager接口;而对于响应式事务管理(reactive transaction management),则使用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是一个接口,因此可以根据需要轻松地进行模拟或替换(mock/stub)。它不依赖于任何查找策略,比如JNDI。PlatformTransactionManager的实现与Spring框架IoC容器中的其他对象(或bean)一样进行定义。仅这一点就使得Spring框架的事务抽象非常有价值,即使你在使用JTA时也是如此。与直接使用JTA相比,你可以更轻松地测试事务代码。
再次强调,遵循Spring的哲学,PlatformTransactionManager接口的任何方法都可能抛出TransactionException,而这种异常是未被检查的(即它继承自java.lang.RuntimeException类)。事务基础设施的故障几乎总是致命的。在极少数情况下,应用程序代码确实可以从事务故障中恢复,此时应用程序开发者仍然可以选择捕获并处理TransactionException。但关键在于,开发者并不是被强制这样做。
getTransaction(..) 方法根据 TransactionDefinition 参数返回一个 TransactionStatus 对象。返回的 TransactionStatus 可能代表一个新的事务,或者在当前调用栈中存在匹配的事务时,也可以代表一个已存在的事务。在后一种情况下,与 Jakarta EE 事务上下文类似,一个 TransactionStatus 会与一个执行线程关联起来。
Spring还为使用反应型类型(reactive types)或Kotlin协程(Kotlin Coroutines)的反应式应用程序(reactive applications)提供了事务管理抽象。以下列表展示了由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框架或任何事务管理解决方案至关重要。
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。以下示例声明了 sessionFactory 和 txManager bean:
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.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.jpa.hibernate.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
如果你使用Hibernate和Jakarta EE容器管理的JTA事务,你应该使用与之前JTA示例中用于JDBC的相同的JtaTransactionManager,如下例所示。此外,建议通过Hibernate的事务协调器(transaction coordinator)以及可能的连接释放模式配置(connection release mode configuration),让Hibernate能够识别JTA:
<bean id="sessionFactory" class="org.springframework.orm.jpa.hibernate.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.jpa.hibernate.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"/>