理解 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。PlatformTransactionManager
实现像Spring框架IoC容器中的任何其他对象(或Bean)一样定义。仅此一项优势就使Spring框架事务成为一个有价值的抽象,即使您使用JTA也是如此。您可以比直接使用JTA更容易地测试事务代码。
同样,为了保持Spring的理念,任何PlatformTransactionManager
接口方法都可能抛出的TransactionException
都是未经检查的(即,它扩展了java.lang.RuntimeException
类)。事务基础设施故障几乎总是致命的。在应用程序代码实际上可以从事务故障中恢复的极少数情况下,应用程序开发人员仍然可以选择捕获和处理TransactionException
。关键点是开发人员不被迫这样做。
getTransaction(..)
方法返回一个TransactionStatus
对象,具体取决于TransactionDefinition
参数。返回的TransactionStatus
可能表示一个新的事务,或者如果在当前调用栈中存在匹配的事务,则可以表示一个现有事务。在这种后一种情况下的含义是,与Jakarta EE事务上下文一样,TransactionStatus
与执行线程相关联。
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的定义使用了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.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"/>