编程式事务管理

TODAY Framework 提供了两种编程式事务管理的方法,通过使用:

  • TransactionTemplateTransactionalOperator

  • 直接使用 TransactionManager 实现。

Infra 团队通常建议使用 TransactionTemplate 进行命令式流中的编程式事务管理, 并建议使用 TransactionalOperator 进行响应式代码。 第二种方法类似于使用 JTA UserTransaction API,尽管异常处理不那么繁琐。

使用 TransactionTemplate

TransactionTemplate 采用与其他 Infra 模板(如 JdbcTemplate)相同的方法。 它使用回调方法(使应用程序代码免于执行样板化的获取和释放事务资源),并产生意图驱动的代码, 即您的代码仅专注于您想要做的事情。

如下面的示例所示,使用 TransactionTemplate 绝对会将您耦合到 Infra 事务基础设施和 API。 编程式事务管理是否适合您的开发需求是您必须自己做出的决定。

必须在事务上下文中运行并显式使用 TransactionTemplate 的应用程序代码类似于下一个示例。 作为应用程序开发人员,您可以编写一个 TransactionCallback 实现(通常表示为匿名内部类), 其中包含您需要在事务上下文中运行的代码。然后,您可以将自定义 TransactionCallback 的实例传递给 TransactionTemplate 上公开的 execute(..) 方法。以下示例展示了如何执行此操作:

  • Java

public class SimpleService implements Service {

  // 在此实例的所有方法之间共享的单个 TransactionTemplate
  private final TransactionTemplate transactionTemplate;

  // 使用构造函数注入来提供 PlatformTransactionManager
  public SimpleService(PlatformTransactionManager transactionManager) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);
  }

  public Object someServiceMethod() {
    return transactionTemplate.execute(new TransactionCallback() {
      // 此方法中的代码在事务上下文中运行
      public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
      }
    });
  }
}

如果没有返回值,您可以使用方便的 TransactionCallbackWithoutResult 类和匿名类,如下所示:

  • Java

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
  protected void doInTransactionWithoutResult(TransactionStatus status) {
    updateOperation1();
    updateOperation2();
  }
});

回调中的代码可以通过在提供的 TransactionStatus 对象上调用 setRollbackOnly() 方法来回滚事务,如下所示:

  • Java

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

  protected void doInTransactionWithoutResult(TransactionStatus status) {
    try {
      updateOperation1();
      updateOperation2();
    } catch (SomeBusinessException ex) {
      status.setRollbackOnly();
    }
  }
});

指定事务设置

您可以通过编程方式或在配置中指定 TransactionTemplate 上的事务设置(例如传播模式、隔离级别、超时等)。 默认情况下,TransactionTemplate 实例具有 默认事务设置。 以下示例显示了特定 TransactionTemplate 的事务设置的编程式自定义:

  • Java

public class SimpleService implements Service {

  private final TransactionTemplate transactionTemplate;

  public SimpleService(PlatformTransactionManager transactionManager) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);

    // 如果需要,可以在此处显式设置事务设置
    this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    this.transactionTemplate.setTimeout(30); // 30 seconds
    // 等等...
  }
}

以下示例使用 Infra XML 配置定义具有一些自定义事务设置的 TransactionTemplate

<bean id="sharedTransactionTemplate"
    class="infra.transaction.support.TransactionTemplate">
  <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
  <property name="timeout" value="30"/>
</bean>

然后,您可以将 sharedTransactionTemplate 注入到所需数量的服务中。

最后,TransactionTemplate 类的实例是线程安全的,因为实例不维护任何会话状态。 但是,TransactionTemplate 实例确实维护配置状态。因此,虽然许多类可以共享 TransactionTemplate 的单个实例, 但如果一个类需要使用具有不同设置(例如,不同的隔离级别)的 TransactionTemplate, 则需要创建两个不同的 TransactionTemplate 实例。

使用 TransactionalOperator

TransactionalOperator 遵循类似于其他响应式操作符的操作符设计。它使用回调方法 (使应用程序代码免于执行样板化的获取和释放事务资源),并产生意图驱动的代码, 即您的代码仅专注于您想要做的事情。

如下面的示例所示,使用 TransactionalOperator 绝对会将您耦合到 Infra 事务基础设施和 API。 编程式事务管理是否适合您的开发需求是您必须自己做出的决定。

必须在事务上下文中运行并显式使用 TransactionalOperator 的应用程序代码类似于下一个示例:

  • Java

public class SimpleService implements Service {

  // 在此实例的所有方法之间共享的单个 TransactionalOperator
  private final TransactionalOperator transactionalOperator;

  // 使用构造函数注入来提供 ReactiveTransactionManager
  public SimpleService(ReactiveTransactionManager transactionManager) {
    this.transactionalOperator = TransactionalOperator.create(transactionManager);
  }

  public Mono<Object> someServiceMethod() {

    // 此方法中的代码在事务上下文中运行
    Mono<Object> update = updateOperation1();

    return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
  }
}

TransactionalOperator 可以通过两种方式使用:

  • 使用 Project Reactor 类型的操作符风格(mono.as(transactionalOperator::transactional)

  • 适用于所有其他情况的回调风格(transactionalOperator.execute(TransactionCallback<T>)

回调中的代码可以通过在提供的 ReactiveTransaction 对象上调用 setRollbackOnly() 方法来回滚事务,如下所示:

  • Java

transactionalOperator.execute(new TransactionCallback<>() {

  public Mono<Object> doInTransaction(ReactiveTransaction status) {
    return updateOperation1().then(updateOperation2)
          .doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
    }
  }
});

取消信号

在 Reactive Streams 中,Subscriber 可以取消其 Subscription 并停止其 Publisher。 Project Reactor 以及其他库中的操作符,例如 next()take(long)timeout(Duration) 等,都可以发出取消信号。 无法知道取消的原因,是由于错误还是仅仅因为缺乏进一步消费的兴趣。 从 5.3 版本开始,取消信号会导致回滚。 因此,重要的是要考虑在事务 Publisher 下游使用的操作符。 特别是在 Flux 或其他多值 Publisher 的情况下,必须使用完整的输出以允许事务完成。

指定事务设置

您可以为 TransactionalOperator 指定事务设置(例如传播模式、隔离级别、超时等)。 默认情况下,TransactionalOperator 实例具有 默认事务设置。 以下示例显示了特定 TransactionalOperator 的事务设置的自定义:

  • Java

public class SimpleService implements Service {

  private final TransactionalOperator transactionalOperator;

  public SimpleService(ReactiveTransactionManager transactionManager) {
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

    // 如果需要,可以在此处显式设置事务设置
    definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    definition.setTimeout(30); // 30 seconds
    // 等等...

    this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
  }
}

使用 TransactionManager

以下部分解释了命令式和响应式事务管理器的编程式用法。

使用 PlatformTransactionManager

对于命令式事务,您可以直接使用 infra.transaction.PlatformTransactionManager 来管理您的事务。 为此,请通过 bean 引用将您使用的 PlatformTransactionManager 的实现传递给您的 bean。 然后,通过使用 TransactionDefinitionTransactionStatus 对象,您可以启动事务、回滚和提交。 以下示例展示了如何执行此操作:

  • Java

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 显式设置事务名称是只能通过编程方式完成的事情
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
  // 将您的业务逻辑放在这里
} catch (MyException ex) {
  txManager.rollback(status);
  throw ex;
}
txManager.commit(status);

使用 ReactiveTransactionManager

在使用响应式事务时,您可以直接使用 infra.transaction.ReactiveTransactionManager 来管理您的事务。 为此,请通过 bean 引用将您使用的 ReactiveTransactionManager 的实现传递给您的 bean。 然后,通过使用 TransactionDefinitionReactiveTransaction 对象,您可以启动事务、回滚和提交。 以下示例展示了如何执行此操作:

  • Java

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 显式设置事务名称是只能通过编程方式完成的事情
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);

reactiveTx.flatMap(status -> {

  Mono<Object> tx = ...; // 将您的业务逻辑放在这里

  return tx.then(txManager.commit(status))
      .onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});