带有Java 8 CompletableFuture的Spring TransactionInterceptor

时间:2017-11-18 03:10:23

标签: spring java-8 spring-data spring-transactions completable-future

我们在业务层方法上使用Spring @Transactional注释。当我们转移到Java 8时,这些方法已经转换为使用Java 8的Async功能并使用可完成的Futures并将几个异步调用链接到数据层。例如。

@Transactional
public CompletableFuture<Entity> updateEntity(ID id) {
   repository1.get(id)
      .thenComposeAsync(item -> repository1.save(), executor); 
}

上面的代码演示了我们想要链接多个数据库层异步调用。

@Transactional注释似乎仅支持阻止调用,并且所有事务上下文信息都保存在ThreadLocal中。上述方法创建了3个事务。一旦返回未来,外部事务就会启动并完成。 repository1.get(id)在另一个交易中运行,repository1.save()在其自己的交易中运行。

是否有一种标准方法可以在单个事务中运行多个异步调用,而无需重写事务拦截器。似乎我们需要将TransactionSynchronizationManager中的线程局部变量从一个线程复制到另一个线程,以使其在单个事务中执行。
如果他们计划增强spring-tx以支持这个用例,那么感谢Spring团队的反馈。

1 个答案:

答案 0 :(得分:0)

我在problem I was trying to solve中遇到了类似的障碍。

简而言之:

  1. 最简单和最effective way to solve this是通过生产者 - 消费者模式(并管道到单个事务工作者线程)。见下面的例子。
  2. 如果您的问题可以解决,可以使用batching
  3. 你可以自然地用JTA解决这个问题,根据定义,事务管理器处理这个问题(正如你所问spring-tx has supported since 2003),这会给你带来比你讨价还价更多的开销(和复杂性)对
  4. 摆弄交易拦截器和TransactionSynchronizationManager并通过黑客解决方法解决,大多数TransactionManagers都不是为了应对
  5. 而构建的

    我在单个线程中的事务对象看起来大致如下:

    @Service
    public class TransactionalObject {
    
        @Autowired
        private BlockingQueue<Runnable> queue;
    
        @Transactional
        public void consumeTransactionalRunnables() {
             try {
                    while (!Thread.currentThread().isInterrupted()) {
                        Runnable execution = queue.take();
                        if (execution instanceof StopExecution) {
                            return;
                        }
                        execution.run();
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }               
        }
    
    }
    

    这个对象在它自己的线程上,生成器线程putBlockingQueue queue。此代码示例中的poison pillStopExecution Runnable类型。

    我发现这个解决方案具有最小的性能开销(与其他选项相比),易于管理,易于阅读和微创。