事务提交的顺序正确,并在使用Spring Integration

时间:2017-11-21 15:17:11

标签: transactions spring-integration

这些都是需要满足的要求

  • 链包含在交易中
  • JMS事务需要在数据库事务之前的链末尾提交
  • 如果JMS提交失败,则不应执行数据库提交
  • 在所有成功的数据库提交之后,需要将消息(及其所有操作)放在输出通道上

有问题的链是以下

<int:poller id="myInputChannelPoller">
  <int:transactional transaction-manager="myTransactionManager">
</int:poller>

<int:chain id="myChain" input-channel="myInputChannel">
  <int:poller ref="myInputChannelPoller" />
  <int:service-activtor id="someMessageManipulation" />
  <int:service-activtor id="aDatabaseOperation" />
  <int:service-activtor id="sendJmsMessage" />
  <int:service-activtor id="anotherMessageManipulation" />
  <int:service-activtor id="anotherDatabaseOperation" />
</int:chain>

为了在事务中包装链,我将<int:transactional/>标记添加到轮询器。

在数据库事务之前提交了所有JMS事务,我使用了ChainedTransactionManager

@Bean
public PlatformTransactionManager myTransactionManager(JpaTransactionManager jpaTransactionManager, JmsTransactionmanager jmsTransactionManager) {
        // jpa before jms, since the commits will be done in reverse order
        return new ChainedTransactionManager(jpaTransactionManager, jmsTransactionmanager)
}

当JMS提交失败时,这也确保没有数据库提交。

我不确定如何正确满足的唯一要求是后数据库提交操作。我想到的第一个想法是

中的transaction-synchronization-factory机制
<int:transaction-synchronization-factory id="mySyncFactory">
  <int:after-commit channel="myOutputChannel" />
</int:transaction-synchronization-factory>

并将其链接到现有轮询器的事务

<int:poller id="myInputChannelPoller">
  <int:transactional transaction-manager="myTransactionManager" synchronization-factory="mySyncFactory">
</int:poller>

但是这种方法似乎发送了最初从myInputChannel收到的消息,而没有考虑消息操作。

另一个想法是在链的末尾添加另一个service-activator MyJpaTransactionManagerInjector,它将消息和输出队列作为有效负载添加到JpaTransactionManager MyJpaTransactionManager的子类。

接收消息和目标输出通道作为输入。创建ForwardMessage并将其注入MyJpaTransactionManager

public class MyJpaTransactionManagerInjector {

    @Inject
    private MyJpaTransactionManager myJpaTransactionManager;

    @ServiceActivator
    public void injectMessage(Message<?> message, @Header(value = "outputChannel") MessageChannel outputChannel) {
        myJpaTransactionManager.inject(new ForwardMessage(message, outputChannel));
    }
}

在执行提交之前注册ForwardMessage

public class MyJpaTransactionManager extends JpaTransactionManager {

    private final ThreadLocal<List<TransactionSynchronizationAdapter>> synchronizations =
            new NamedThreadLocal<>("Transactional resources");

    public void inject(TransactionSynchronizationAdapter synchronization) {
        if (synchronizations.get() == null) {
            synchronizations.set(new ArrayList<>());
        }
        synchronizations.get().add(synchronization);
    }

    @Override
    protected void prepareForCommit(DefaultTransactionStatus status) {            
        super.prepareForCommit(status);
        if (synchronizations.get() != null) {
            synchronizations.get().forEach(TransactionSynchronizationManager::registerSynchronization);
        }
        synchronizations.remove();
    }
}

afterCommit()上的消息转发至目标渠道

public class ForwardMessage extends TransactionSynchronizationAdapter {

    private Message<?> message;
    private MessageChannel outputChannel;

    public ForwardMessage(Message<?> message, MessageChannel outputChannel) {
        this.message = message;
        this.outputChannel = outputChannel;
    }

    @Override
    public void afterCommit() {
        outputChannel.send(message);
    }
}

只要有可能,我会避免继承TransactionManager,因为它很容易打破一些效果非常好的东西。有没有一个解决方案应该优于这个?

1 个答案:

答案 0 :(得分:1)

我仍然坚持使用<int:transaction-synchronization-factory>

PollingConsumer注册了这个:

TransactionSynchronizationManager.bindResource(resource,
                                integrationSynchronization.getResourceHolder());

进入交易。

其中resource是消费者的MessageChannel。在你的情况下,它是myInputChannel bean。

您可以使用以下内容:

IntegrationResourceHolder integrationResourceHolder = (IntegrationResourceHolder) TransactionSynchronizationManager.getResource(myInputChannel);

并在处理后调用其addAttribute()来存储消息:

integrationResourceHolder.addAttribute("resultMessage", resultMessage);

之后您仍然可以使用<int:after-commit channel="myOutputChannel" />并通过适当的SpEL变量访问该属性:

<int:after-commit channel="myOutputChannel"  expression="#resultMessage"/>