Spring 事务抽象的关键是事务策略的概念。事务策略是由 TransactionManager 定义的,特别是org.springframework.transaction.PlatformTransactionManager接口,用于强制性事务管理,org.springframework.transaction.ReactiveTransactionManager接口用于反应性事务管理。下面的列表显示了 PlatformTransactionManager API 的定义。

  1. public interface PlatformTransactionManager extends TransactionManager {
  2. // 获取一个事物
  3. TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
  4. // 提交事物
  5. void commit(TransactionStatus status) throws TransactionException;
  6. // 回滚事务
  7. void rollback(TransactionStatus status) throws TransactionException;
  8. }

这主要是一个服务提供者接口(SPI),尽管你可以从你的应用代码中以编程方式使用它。因为 PlatformTransactionManager 是一个接口,它可以很容易地被模拟或在必要时被存根。它不与查找策略相联系,如 JNDI。PlatformTransactionManager 的实现与 Spring Framework IoC 容器中的其他对象(或 Bean)一样被定义。仅仅是这个好处就使 Spring 框架的事务成为一个值得一试的抽象,即使是在你使用 JTA 的时候。你可以比直接使用 JTA 更容易地测试事务性代码。

同样,为了与 Spring 的理念保持一致,可以由 PlatformTransactionManager 接口的任何方法抛出的 TransactionException 是未经检查的(也就是说,它继承了 java.lang.RuntimeException 类)。事物基础设施的失败几乎无一例外都是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复过来,应用开发者仍然可以选择捕捉和处理 TransactionException。突出的一点是,开发者并不被迫这样做。

getTransaction(...)方法返回一个 TransactionStatus 对象,这取决于 TransactionDefinition 参数。返回的 TransactionStatus 可能代表一个新的事务,也可能代表一个现有的事务,如果在当前调用栈中存在一个匹配的事务。后者的含义是,与 Java EE 的事务上下文一样,TransactionStatus 与 执行线程相关

从 Spring Framework 5.2 开始,Spring 也为利用反应式类型或 Kotlin Coroutines 的反应式应用提供了一个事务管理抽象。下面的列表显示了org.springframework.transaction.ReactiveTransactionManager所定义的事务策略:

  1. public interface ReactiveTransactionManager extends TransactionManager {
  2. Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
  3. Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
  4. Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
  5. }

反应式事务管理器主要是一个服务提供者接口(SPI),尽管你可以从你的应用程序代码中以编程方式使用它。因为ReactiveTransactionManager 是一个接口,它可以根据需要很容易地被模拟或存根。

TransactionDefinition 接口规范:

  • Propagation(传播):通常情况下,一个事务范围内的所有代码都在该事务中运行。然而,如果一个事务性方法在一个已经存在的事务上下文中被运行,你可以指定其行为。例如,代码可以继续在现有的事务中运行(常见的情况),或者暂停现有的事务并创建一个新的事务。Spring 提供了所有从 EJB CMT 中熟悉的事务传播选项。要了解 Spring 中事务传播的语义,请看 事务传播

  • Isolation (隔离):该事务与其他事务的工作隔离的程度。例如,这个事务能否看到其他事务的未提交的写操作?

  • Timeout (超时):该事务运行多久才会超时并被底层事务基础设施自动回滚。

  • Read-only status(只读状态):当你的代码读取但不修改数据时,你可以使用一个只读事务。只读事务在某些情况下是一种有用的优化,例如当你使用 Hibernate 时。

这些设置反映了标准的事务性概念。如果有必要,请参考讨论事务隔离级别和其他核心事务概念的资源。了解这些概念对于使用 Spring 框架或任何事务管理解决方案都是至关重要的。

TransactionStatus 接口为事务代码提供了一种简单的方式来控制 事务执行 查询事务 状态。这些概念应该是熟悉的,因为它们是所有事务API 所共有的。下面的列表显示了 TransactionStatus 接口:

  1. public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
  2. @Override
  3. boolean isNewTransaction();
  4. boolean hasSavepoint();
  5. @Override
  6. void setRollbackOnly();
  7. @Override
  8. boolean isRollbackOnly();
  9. void flush();
  10. @Override
  11. boolean isCompleted();
  12. }

无论你在 Spring 中选择声明式还是程序式事务管理,定义正确的 TransactionManager 实现是绝对必要的。你通常通过依赖性注入来定义这个实现。

TransactionManager 的实现通常需要对其工作环境的了解。JDBC、JTA、Hibernate,等等。下面的例子显示了你如何定义一个本地的PlatformTransactionManager 实现(在这种情况下,用普通的 JDBC。)

你可以通过创建一个与下面类似的 bean 来定义 JDBC 数据源:

  1. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  2. <property name="driverClassName" value="${jdbc.driverClassName}" />
  3. <property name="url" value="${jdbc.url}" />
  4. <property name="username" value="${jdbc.username}" />
  5. <property name="password" value="${jdbc.password}" />
  6. </bean>

然后,相关的 PlatformTransactionManager Bean 定义有一个对 DataSource 定义的引用。它应该类似于下面的例子:

  1. <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  2. <property name="dataSource" ref="dataSource"/>
  3. </bean>

如果你在 Java EE 容器中使用 JTA,那么你就使用通过 JNDI 获得的容器数据源,并与 Spring 的 JtaTransactionManager 结合。下面的例子显示了 JTA 和 JNDI 的查询版本是什么样子的:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:jee="http://www.springframework.org/schema/jee"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. https://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/jee
  9. https://www.springframework.org/schema/jee/spring-jee.xsd">
  10. <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
  11. <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
  12. <!-- other <bean/> definitions here -->
  13. </beans>

JtaTransactionManager 不需要知道数据源(或任何其他特定资源),因为它使用容器的全局事务管理基础设施。

:::info 前面的 dataSource Bean 的定义使用了 jee 命名空间的 <jndi-lookup/>标签。欲了解更多信息,请参见 JEE 模式

如果你使用 JTA,你的事务管理器定义应该看起来是一样的,无论你使用什么数据访问技术,无论是 JDBC、Hibernate JPA,还是其他任何支持的技术。这是由于 JTA 事务是全局事务,它可以征集任何事务性资源。 :::

在所有 Spring 事务设置中,应用程序代码不需要改变。你可以仅仅通过改变配置来改变事务的管理方式,即使这种改变意味着从本地事务转移到全局事务,或者反之亦然。

Hibernate 事务设置

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

数据源 Bean 的定义与之前显示的本地 JDBC 例子相似,因此,在下面的例子中没有显示。

:::info 如果数据源(由任何非 JTA 事务管理器使用)通过 JNDI 查找并由 Java EE 容器管理,它应该是非事务性的,因为 Spring 框架(而不是Java EE 容器)管理着事务。 :::

本例中的 txManager Bean 是 HibernateTransactionManager 类型的。与 DataSourceTransactionManager 需要对 DataSource 的引用一样, HibernateTransactionManager 需要对 SessionFactory 的引用。下面的例子声明了 sessionFactory 和 txManager bean:

  1. <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
  2. <property name="dataSource" ref="dataSource"/>
  3. <property name="mappingResources">
  4. <list>
  5. <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
  6. </list>
  7. </property>
  8. <property name="hibernateProperties">
  9. <value>
  10. hibernate.dialect=${hibernate.dialect}
  11. </value>
  12. </property>
  13. </bean>
  14. <bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
  15. <property name="sessionFactory" ref="sessionFactory"/>
  16. </bean>

如果你使用 Hibernate 和 Java EE 容器管理的 JTA 事务,你应该使用与前面 JDBC 的 JTA 例子中相同的 JtaTransactionManager,如下例所示。另外,建议通过 Hibernate 的事务协调器以及可能的连接释放模式配置,让 Hibernate 知道 JTA:

  1. <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
  2. <property name="dataSource" ref="dataSource"/>
  3. <property name="mappingResources">
  4. <list>
  5. <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
  6. </list>
  7. </property>
  8. <property name="hibernateProperties">
  9. <value>
  10. hibernate.dialect=${hibernate.dialect}
  11. hibernate.transaction.coordinator_class=jta
  12. hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
  13. </value>
  14. </property>
  15. </bean>
  16. <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

或者,你可以将 JtaTransactionManager 传递给你的 LocalSessionFactoryBean,以执行相同的默认值:

  1. <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
  2. <property name="dataSource" ref="dataSource"/>
  3. <property name="mappingResources">
  4. <list>
  5. <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
  6. </list>
  7. </property>
  8. <property name="hibernateProperties">
  9. <value>
  10. hibernate.dialect=${hibernate.dialect}
  11. </value>
  12. </property>
  13. <property name="jtaTransactionManager" ref="txManager"/>
  14. </bean>
  15. <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>