# 事务支持

# 事务支持

本章介绍 Spring 集成对事务的支持。它涵盖以下主题:

# 理解消息流中的事务

Spring 集成公开了几个钩子,以满足消息流的事务需求。为了更好地理解这些钩子以及如何从中受益,我们必须首先重新审视可以用来启动消息流的六种机制,并了解如何在这些机制中的每一种中解决这些流的事务需求。

以下六种机制启动消息流(本手册提供了每种机制的详细信息):

  • Gateway Proxy:一种基本的消息传递网关。

  • 消息通道:与MessageChannel方法直接交互(例如,channel.send(message))。

  • Message Publisher:在 Spring bean 上作为方法调用的副产品发起消息流的方法。

  • 入站通道适配器和网关:发起消息流的方式基于将第三方系统与 Spring 集成消息传递系统的连接(例如,[JmsMessage] → Jms Inbound Adapter[SI Message] → SI Channel)。

  • 调度器:根据预先配置的调度器分发的调度事件发起消息流的方法。

  • Poller:与调度器类似,这是一种基于调度或由预先配置的 Poller 分发的基于间隔的事件发起消息流的方法。

我们可以将这六种机制分为两大类:

  • 由用户进程发起的消息流:此类别中的示例场景将调用网关方法或显式地将Message发送到MessageChannel。换句话说,这些消息流依赖于要发起的第三方进程(例如你编写的某些代码)。

  • 由守护进程启动的消息流:此类别中的示例场景包括一个 Poller 轮询消息队列以启动带有轮询消息的新消息流,或者一个调度器通过创建新消息并在预定义的时间启动消息流来调度该进程。

显然,网关代理、MessageChannel.send(…​)MessagePublisher都属于第一类,入站适配器和网关、调度器和 poller 属于第二类。

那么,如何在每个类别中的不同场景中解决事务需求,是否需要 Spring 集成来为特定场景提供有关事务的明确内容?或者,你可以使用 Spring 的事务支持吗?

Spring 其本身为事务管理提供了一流的支持。因此,我们在这里的目标不是提供新的东西,而是使用 Spring 来受益于其现有的事务支持。换句话说,作为一个框架,我们必须将钩子公开到 Spring 的事务管理功能。然而,由于 Spring 集成配置是基于 Spring 配置的,因此我们不需要总是公开这些钩子,因为 Spring 已经公开了它们。毕竟,每个 Spring 集成组件都是 Spring Bean。

考虑到这个目标,我们可以再次考虑两个场景:由用户进程发起的消息流和由守护进程发起的消息流。

由用户进程发起并在 Spring 应用程序上下文中配置的消息流受到此类进程的通常事务配置的约束。因此,不需要通过 Spring 集成显式地配置它们来支持事务。交易可以也应该通过 Spring 的标准交易支持来启动。 Spring 集成消息流自然尊重组件的事务语义,因为它本身由 Spring 配置。例如,网关或服务激活器方法可以用@Transactional进行注释,或者可以在 XML 配置中定义TransactionInterceptor,并使用切入点表达式指向应该是事务性的特定方法。最重要的是,在这些场景中,你可以完全控制事务配置和边界。

然而,当涉及到由守护进程发起的消息流时,情况就有些不同了。尽管由开发人员进行了配置,但这些流并不直接涉及要启动的人工或其他进程。这些是基于触发器的流,由触发器进程(守护进程)基于进程的配置发起。例如,我们可以让调度器在每个星期五晚上发起消息流。我们还可以配置一个触发器,该触发器每秒钟启动一次消息流,以此类推。因此,我们需要一种方法,让这些基于触发器的流程知道我们的意图,即使生成的消息流是事务性的,以便每当启动新的消息流时都可以创建事务上下文。换句话说,我们需要公开一些事务配置,但仅限于委托给 Spring 已经提供的事务支持(就像我们在其他场景中所做的那样)。

# Poller 事务支持

Spring 集成为 Pollers 提供了事务支持。Poller 是一种特殊类型的组件,因为在 Poller 任务中,我们可以针对本身是事务性的资源调用receive(),从而在事务的边界中包括receive()调用,这使它在任务失败的情况下被回滚。如果我们要为通道添加相同的支持,那么添加的事务将影响从send()调用开始的所有下游组件。这为事务划分提供了相当广泛的范围,而没有任何强有力的理由,特别是当 Spring 已经提供了几种方法来解决下游任何组件的事务需求时。然而,将receive()方法包含在事务边界中,是 Pollers 的“有力理由”。

在配置 Poller 的任何时候,都可以通过使用transactional子元素及其属性来提供事务配置,如下例所示:

<int:poller max-messages-per-poll="1" fixed-rate="1000">
    <transactional transaction-manager="txManager"
                   isolation="DEFAULT"
                   propagation="REQUIRED"
                   read-only="true"
                   timeout="1000"/>
</poller>

前面的配置看起来类似于本机 Spring 事务配置。你仍然必须提供对事务管理器的引用,并且要么指定事务属性,要么依赖默认值(例如,如果没有指定“transactionmanager”属性,则默认使用 Bean 命名的“transactionmanager”)。在内部,该过程被包装在 Spring 的本机事务中,其中TransactionInterceptor负责处理事务。有关如何配置事务管理器、事务管理器的类型(例如 JTA、DataSource 和其他)以及与事务配置有关的其他详细信息,请参见Spring Framework Reference Guide (opens new window)

在前面的配置中,由这个 Poller 发起的所有消息流都是事务性的。有关 Poller 事务配置的更多信息和详细信息,请参见轮询和交易

在运行 Poller 时,除了事务之外,你可能还需要解决几个更多的交叉问题。为了帮助实现这一点,Poller 元素接受一个<advice-chain>子元素,它允许你定义一个定制的建议实例链,以应用于 Poller。(有关更多详细信息,请参见可选消息源。)在 Spring Integration2.0 中,Poller 进行了重构工作,现在使用代理机制来解决事务性问题以及其他跨领域问题。这项工作产生的一个重大变化是,我们使<transactional><advice-chain>元素是互斥的。这背后的理由是,如果你需要一个以上的建议,其中一个是事务建议,那么你可以将其包含在<advice-chain>中,这样做与以前一样方便,但具有更多的控制权,因为你现在可以选择将建议按所需的顺序放置。下面的示例展示了如何做到这一点:

<int:poller max-messages-per-poll="1" fixed-rate="10000">
  <advice-chain>
    <ref bean="txAdvice"/>
    <ref bean="someOtherAdviceBean" />
    <beans:bean class="foo.bar.SampleAdvice"/>
  </advice-chain>
</poller>

<tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
    <tx:method name="get*" read-only="true"/>
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

前面的示例显示了 Spring 事务通知(txAdvice)的基本基于 XML 的配置,并将其包含在由 Poller 定义的<advice-chain>中。如果只需要解决 Poller 的事务问题,仍然可以使用<transactional>元素作为方便。

# 事务边界

另一个重要因素是消息流中事务的边界。当事务启动时,事务上下文被绑定到当前线程。因此,无论消息流中有多少个端点和通道,只要确保消息流在同一线程上继续,事务上下文都将被保留。一旦你通过引入可选频道执行器通道来打破它,或者在某些服务中手动启动一个新线程,事务边界也将被打破。从本质上讲,事务将在那里结束,如果在线程之间进行了成功的切换,那么流将被认为是成功的,并且将发送一个提交信号,即使流将继续,并且仍然可能在下游某个地方导致异常。如果这样的流是同步的,那么该异常将被抛回消息流的发起者,该发起者也是事务上下文的发起者,事务将导致回滚。中间立场是在线程边界被打破的任何一点使用事务通道。例如,你可以使用一个队列支持的通道,该通道将委托给事务性 MessageStore 策略,或者你可以使用一个 JMS 支持的通道。

# 事务同步

在某些环境中,它有助于将操作与包含整个流程的事务同步。例如,考虑在执行多个数据库更新的流的开始处使用<file:inbound-channel-adapter/>。如果事务提交,我们可能希望将文件移动到success目录,而如果事务回滚,我们可能希望将文件移动到failure目录。

Spring Integration2.2 引入了将这些操作与事务同步的能力。此外,如果你没有“实际”事务,但仍然希望在成功或失败时执行不同的操作,则可以配置PseudoTransactionManager。有关更多信息,请参见伪交易

下面的清单显示了该特性的关键策略接口:

public interface TransactionSynchronizationFactory {

    TransactionSynchronization create(Object key);
}

public interface TransactionSynchronizationProcessor {

    void processBeforeCommit(IntegrationResourceHolder holder);

    void processAfterCommit(IntegrationResourceHolder holder);

    void processAfterRollback(IntegrationResourceHolder holder);

}

工厂负责创建一个[TransactionSynchronization](https://DOCS. Spring.io/ Spring-framework/DOCS/current/javadoc-api/org/springframework/transaction/support/transactionsynchronization.html)对象。你可以实现自己的或使用框架提供的:DefaultTransactionSynchronizationFactory。此实现返回一个TransactionSynchronization,该实现将委托给默认的TransactionSynchronizationProcessor实现:ExpressionEvaluatingTransactionSynchronizationProcessor。此处理器支持三个 SPEL 表达式:beforeCommitExpressionafterCommitExpressionafterRollbackExpression

对于那些熟悉事务的人来说,这些操作应该是不言自明的。在每种情况下,#root变量都是原始的Message。在某些情况下,其他 SPEL 变量是可用的,这取决于由 poller 进行民意测验的MessageSource。例如,MongoDbMessageSource提供了#mongoTemplate变量,该变量引用消息源的MongoTemplate。类似地,RedisStoreMessageSource提供了#store变量,该变量引用了轮询创建的RedisStore

要为特定的 Poller 启用该特性,你可以使用synchronization-factory属性,在 Poller 的TransactionSynchronizationFactory元素上提供对TransactionSynchronizationFactory的引用。

从版本 5.0 开始, Spring Integration 提供,当没有配置,但在建议链中存在类型的建议时,默认情况下将其应用于轮询端点。当使用任何开箱即用的TransactionSynchronizationFactory实现时,轮询端点将轮询消息绑定到当前事务上下文,并在MessagingException中将其作为failedMessage提供,如果在事务通知之后抛出了异常。当使用不实现TransactionInterceptor的自定义事务建议时,可以显式地配置PassThroughTransactionSynchronizationFactory以实现此行为。在这两种情况下,MessagingException都成为发送到ErrorMessageerrorChannel的有效负载,而原因是建议引发的原始异常。在此之前,ErrorMessage的有效负载是由建议引发的原始异常,并且不提供对failedMessage信息的引用,这使得很难确定事务提交问题的原因。

Spring 为了简化这些组件的配置,集成为默认工厂提供了名称空间支持。下面的示例展示了如何使用名称空间来配置文件入站通道适配器:

<int-file:inbound-channel-adapter id="inputDirPoller"
    channel="someChannel"
    directory="/foo/bar"
    filter="filter"
    comparator="testComparator">
    <int:poller fixed-rate="5000">
        <int:transactional transaction-manager="transactionManager" synchronization-factory="syncFactory" />
    </int:poller>
</int-file:inbound-channel-adapter>

<int:transaction-synchronization-factory id="syncFactory">
    <int:after-commit expression="payload.renameTo(new java.io.File('/success/' + payload.name))"
        channel="committedChannel" />
    <int:after-rollback expression="payload.renameTo(new java.io.File('/failed/' + payload.name))"
        channel="rolledBackChannel" />
</int:transaction-synchronization-factory>

SPEL 评估的结果作为有效负载发送到committedChannelrolledBackChannel(在这种情况下,这将是Boolean.TRUEBoolean.FALSE——java.io.File.renameTo()方法调用的结果)。

如果你希望将整个有效载荷发送给进一步的 Spring 集成处理,请使用’payload’表达式。

重要的是要理解这会使操作与事务同步,
它不会使本质上不是事务性的资源实际上是事务性的。,相反,
,事务(无论是 JDBC 还是其他方式)在轮询之前启动,并在流完成时提交或回滚,然后是同步操作。,如果提供自定义

,它负责创建一个资源同步,该同步使绑定的资源在事务完成时自动解除绑定。
默认值TransactionSynchronizationFactory是通过返回一个ResourceHolderSynchronization的子类来实现的,默认值shouldUnbindAtCompletion()返回true

除了after-commitafter-rollback表达式外,还支持before-commit表达式。在这种情况下,如果评估(或下游处理)抛出异常,事务将被回滚而不是提交。

# 伪事务

在阅读了事务同步部分之后,你可能会认为在流完成时执行这些“成功”或“失败”操作是有用的,即使在 Poller 的下游没有“真正的”事务资源(例如 JDBC)。例如,考虑一个“<file:inbound-channel-adapter/>”后跟一个“<ftp:outbout-channel-adapter/>”。这两个组件都不是事务性的,但是我们可能希望根据 FTP 传输的成功或失败,将输入文件移动到不同的目录。

为了提供这种功能,框架提供了PseudoTransactionManager,即使在不涉及真正的事务资源的情况下,也能够实现上述配置。如果流正常完成,则调用beforeCommitafterCommit同步。一旦失败,将调用afterRollback同步。因为它不是一个真正的事务,所以不会发生实际的提交或回滚。伪事务是一种用来实现同步功能的载体。

要使用PseudoTransactionManager,可以将其定义为 <bean/>,就像配置实际事务管理器一样。下面的示例展示了如何做到这一点:

<bean id="transactionManager" class="o.s.i.transaction.PseudoTransactionManager" />

# 无反应事务

从版本 5.3 开始,对于返回反应类型的端点,ReactiveTransactionManager通知也可以与TransactionInterceptor通知一起使用。这包括MessageSourceReactiveMessageHandler实现(例如ReactiveMongoDbMessageSource),它们产生带有FluxMono有效载荷的消息。当它们的应答负载也是某种反应性类型时,所有其他产生应答的消息处理程序实现都可以依赖于ReactiveTransactionManager